Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enh/add virtual group #1687

Merged
merged 9 commits into from
Aug 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions appinfo/app.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@
use OCA\Contacts\AppInfo\Application;

$app = \OC::$server->query(Application::class);
$app->register();
4 changes: 4 additions & 0 deletions appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@
<bugs>https://github.com/nextcloud/contacts/issues</bugs>
<repository type="git">https://github.com/nextcloud/contacts.git</repository>

<!-- required for dav plugins registration -->
<types>
<filesystem/>
</types>

<dependencies>
<nextcloud min-version="17" max-version="20" />
Expand Down
14 changes: 0 additions & 14 deletions css/ContactsList.scss
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,3 @@
border-radius: 50%;
opacity: 1;
}

// Virtual scroller overrides
.vue-recycle-scroller {
position: sticky !important;
}

.vue-recycle-scroller__item-view {
// TODO: find better solution?
// https://github.com/Akryum/vue-virtual-scroller/issues/70
// hack to not show the transition
overflow: hidden;
// same as app-content-list-item
height: 68px;
}
1 change: 1 addition & 0 deletions css/icons.scss
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
@include icon-black-white('language', 'contacts', 2);
@include icon-black-white('clone', 'contacts', 2);
@include icon-black-white('sync', 'contacts', 2);
@include icon-black-white('recent-actors', 'contacts', 1);

// social network icons:
@include icon-black-white('facebook', 'contacts', 2); // “facebook (fab)” by fontawesome.com is licensed under CC BY 4.0. (https://fontawesome.com/icons/facebook?style=brands)
Expand Down
1 change: 1 addition & 0 deletions img/recent-actors.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 24 additions & 4 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,36 @@
*/
namespace OCA\Contacts\AppInfo;

use OCA\Contacts\Dav\PatchPlugin;
use OCP\AppFramework\App;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\SabrePluginEvent;

class Application extends App {
public const APP_ID = 'contacts';

public function __construct() {
parent::__construct(self::APP_ID);
}

public const AVAIL_SETTINGS = [
'allowSocialSync' => 'yes',
];

public function __construct() {
parent::__construct(self::APP_ID);
}

public function register() {
$server = $this->getContainer()->getServer();

/** @var IEventDispatcher $eventDispatcher */
$eventDispatcher = $server->query(IEventDispatcher::class);
$eventDispatcher->addListener('OCA\DAV\Connector\Sabre::addPlugin', function (SabrePluginEvent $event) {
$server = $event->getServer();

if ($server !== null) {
// We have to register the LockPlugin here and not info.xml,
// because info.xml plugins are loaded, after the
// beforeMethod:* hook has already been emitted.
$server->addPlugin($this->getContainer()->query(PatchPlugin::class));
}
});
}
}
48 changes: 27 additions & 21 deletions lib/Controller/PageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,16 @@

namespace OCA\Contacts\Controller;

use OCA\Contacts\Service\SocialApiService;
use OCP\App\IAppManager;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\TemplateResponse;

use OCA\Contacts\AppInfo\Application;
use OCA\Contacts\Service\SocialApiService;
use OCP\IConfig;
use OCP\IInitialStateService;
use OCP\IUserSession;
use OCP\IRequest;
use OCP\IUserSession;
use OCP\L10N\IFactory;
use OCP\Util;

Expand All @@ -52,20 +53,24 @@ class PageController extends Controller {
/** @var SocialApiService */
private $socialApiService;

/** @var IAppManager */
private $appManager;

public function __construct(IRequest $request,
IConfig $config,
IInitialStateService $initialStateService,
IFactory $languageFactory,
IUserSession $userSession,
SocialApiService $socialApiService) {
SocialApiService $socialApiService,
IAppManager $appManager) {
parent::__construct(Application::APP_ID, $request);

$this->appName = Application::APP_ID;
$this->config = $config;
$this->initialStateService = $initialStateService;
$this->languageFactory = $languageFactory;
$this->userSession = $userSession;
$this->socialApiService = $socialApiService;
$this->appManager = $appManager;
}

/**
Expand All @@ -82,23 +87,24 @@ public function index(): TemplateResponse {
}

$locales = $this->languageFactory->findAvailableLocales();
$defaultProfile = $this->config->getAppValue($this->appName, 'defaultProfile', 'HOME');
$defaultProfile = $this->config->getAppValue(Application::APP_ID, 'defaultProfile', 'HOME');
$supportedNetworks = $this->socialApiService->getSupportedNetworks();
$syncAllowedByAdmin = $this->config->getAppValue($this->appName, 'allowSocialSync', 'yes'); // allow users to retrieve avatars from social networks (default: yes)
$bgSyncEnabledByUser = $this->config->getUserValue($userId, $this->appName, 'enableSocialSync', 'no'); // automated background syncs for social avatars (default: no)

$this->initialStateService->provideInitialState($this->appName, 'locales', $locales);
$this->initialStateService->provideInitialState($this->appName, 'defaultProfile', $defaultProfile);
$this->initialStateService->provideInitialState($this->appName, 'supportedNetworks', $supportedNetworks);
$this->initialStateService->provideInitialState($this->appName, 'locales', $locales);
$this->initialStateService->provideInitialState($this->appName, 'defaultProfile', $defaultProfile);
$this->initialStateService->provideInitialState($this->appName, 'supportedNetworks', $supportedNetworks);
$this->initialStateService->provideInitialState($this->appName, 'allowSocialSync', $syncAllowedByAdmin);
$this->initialStateService->provideInitialState($this->appName, 'enableSocialSync', $bgSyncEnabledByUser);

Util::addScript($this->appName, 'contacts');
Util::addStyle($this->appName, 'contacts');

return new TemplateResponse($this->appName, 'main');
$syncAllowedByAdmin = $this->config->getAppValue(Application::APP_ID, 'allowSocialSync', 'yes'); // allow users to retrieve avatars from social networks (default: yes)
$bgSyncEnabledByUser = $this->config->getUserValue($userId, Application::APP_ID, 'enableSocialSync', 'no'); // automated background syncs for social avatars (default: no)

$this->initialStateService->provideInitialState(Application::APP_ID, 'locales', $locales);
$this->initialStateService->provideInitialState(Application::APP_ID, 'defaultProfile', $defaultProfile);
$this->initialStateService->provideInitialState(Application::APP_ID, 'supportedNetworks', $supportedNetworks);
$this->initialStateService->provideInitialState(Application::APP_ID, 'locales', $locales);
$this->initialStateService->provideInitialState(Application::APP_ID, 'defaultProfile', $defaultProfile);
$this->initialStateService->provideInitialState(Application::APP_ID, 'supportedNetworks', $supportedNetworks);
$this->initialStateService->provideInitialState(Application::APP_ID, 'allowSocialSync', $syncAllowedByAdmin);
$this->initialStateService->provideInitialState(Application::APP_ID, 'enableSocialSync', $bgSyncEnabledByUser);
$this->initialStateService->provideInitialState(Application::APP_ID, 'contactsinteraction', $this->appManager->isEnabledForUser('contactsinteraction') === true);

Util::addScript(Application::APP_ID, 'contacts');
Util::addStyle(Application::APP_ID, 'contacts');

return new TemplateResponse(Application::APP_ID, 'main');
}
}
186 changes: 186 additions & 0 deletions lib/Dav/PatchPlugin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
<?php

declare(strict_types=1);

/**
* @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\Contacts\Dav;

use Sabre\CardDAV\Card;
use Sabre\DAV;
use Sabre\DAV\INode;
use Sabre\DAV\PropPatch;
use Sabre\DAV\Server;
use Sabre\DAV\ServerPlugin;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
use Sabre\VObject\Component\VCard;
use Sabre\VObject\Reader;

class PatchPlugin extends ServerPlugin {
public const METHOD_REPLACE = 0;
public const METHOD_APPEND = 1;

/** @var Server */
protected $server;

/**
* Initializes the plugin and registers event handlers
*
* @param Server $server
* @return void
*/
public function initialize(Server $server) {
$this->server = $server;
$server->on('method:PATCH', [$this, 'httpPatch']);
}

/**
* Use this method to tell the server this plugin defines additional
* HTTP methods.
*
* This method is passed a uri. It should only return HTTP methods that are
* available for the specified uri.
*
* We claim to support PATCH method (partirl update) if and only if
* - the node exist
* - the node implements our partial update interface
*
* @param string $uri
*
* @return array
*/
public function getHTTPMethods($uri) {
$tree = $this->server->tree;

if ($tree->nodeExists($uri)) {
$node = $tree->getNodeForPath($uri);
if ($node instanceof Card) {
return ['PATCH'];
}
}

return [];
}

/**
* Adds all CardDAV-specific properties
*
* @param PropPatch $propPatch
* @param INode $node
* @return void
*/
public function httpPatch(RequestInterface $request, ResponseInterface $response) {
$path = $request->getPath();
$node = $this->server->tree->getNodeForPath($path);

if (!($node instanceof Card)) {
return true;
}

// Checking ACL, if available.
if ($aclPlugin = $this->server->getPlugin('acl')) {
/** @var \Sabre\DAVACL\Plugin $aclPlugin */
$aclPlugin->checkPrivileges($path, '{DAV:}write');
}

// Init property name & value
$propertyName = $request->getHeader('X-Property');
if (is_null($propertyName)) {
throw new DAV\Exception\BadRequest('No valid "X-Property" found in the headers');
}

$propertyData = $request->getHeader('X-Property-Replace');
$method = self::METHOD_REPLACE;
if (is_null($propertyData)) {
$propertyData = $request->getHeader('X-Property-Append');
$method = self::METHOD_APPEND;
if (is_null($propertyData)) {
throw new DAV\Exception\BadRequest('No valid "X-Property-Append" or "X-Property-Replace" found in the headers');
}
}

// Init contact
$vCard = Reader::read($node->get());
$properties = $vCard->select($propertyName);

// We cannot know which one to update in that case
if (count($properties) > 1) {
throw new DAV\Exception\BadRequest('The specified property appear more than once');
}

// Init if not in the vcard
if (count($properties) === 0) {
$vCard->add($propertyName, $propertyData);
$properties = $vCard->select($propertyName);
}

// Replace existing value
if ($method === self::METHOD_REPLACE) {
$properties[0]->setRawMimeDirValue($propertyData);
}

// Append to existing value
if ($method === self::METHOD_APPEND) {
$oldData = $properties[0]->getValue();
$properties[0]->setRawMimeDirValue($oldData.$propertyData);
}

// Validate & write
$vCard->validate();
$node->put($vCard->serialize());
$response->setStatus(200);

return false;
}

/**
* Returns a plugin name.
*
* Using this name other plugins will be able to access other plugins
* using \Sabre\DAV\Server::getPlugin
*
* @return string
*/
public function getPluginName() {
return 'vcard-patch';
}

/**
* Returns a bunch of meta-data about the plugin.
*
* Providing this information is optional, and is mainly displayed by the
* Browser plugin.
*
* The description key in the returned array may contain html and will not
* be sanitized.
*
* @return array
*/
public function getPluginInfo() {
return [
'name' => $this->getPluginName(),
'description' => 'Allow to patch unique properties.'
];
}
}
Loading