!/apps/accessibility
!/apps/cloud_federation_api
!/apps/comments
+!/apps/contactsinteraction
!/apps/dav
!/apps/files
!/apps/federation
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @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/>.
+ */
+
+\OC::$server->query(\OCA\ContactsInteraction\AppInfo\Application::class);
--- /dev/null
+<?xml version="1.0"?>
+<info xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd">
+ <id>contactsinteraction</id>
+ <name>Contacts Interaction</name>
+ <summary>Manages interaction between users and contacts</summary>
+ <description>Collect data about user and contacts interactions and provide an address book for the data</description>
+ <version>1.0.0</version>
+ <licence>agpl</licence>
+ <author>Christoph Wurst</author>
+ <namespace>ContactsInteraction</namespace>
+ <types>
+ <dav/>
+ </types>
+ <default_enable/>
+ <category>integration</category>
+ <category>social</category>
+ <bugs>https://github.com/nextcloud/server/issues</bugs>
+ <dependencies>
+ <nextcloud min-version="19" max-version="19"/>
+ </dependencies>
+ <background-jobs>
+ <job>OCA\ContactsInteraction\BackgroundJob\CleanupJob</job>
+ </background-jobs>
+ <sabre>
+ <address-book-plugins>
+ <plugin>OCA\ContactsInteraction\AddressBookProvider</plugin>
+ </address-book-plugins>
+ </sabre>
+</info>
--- /dev/null
+<?php
+
+// autoload.php @generated by Composer
+
+require_once __DIR__ . '/composer/autoload_real.php';
+
+return ComposerAutoloaderInitContactsInteraction::getLoader();
--- /dev/null
+{
+ "config" : {
+ "vendor-dir": ".",
+ "optimize-autoloader": true,
+ "classmap-authoritative": true,
+ "autoloader-suffix": "ContactsInteraction"
+ },
+ "autoload" : {
+ "psr-4": {
+ "OCA\\ContactsInteraction\\": "../lib/"
+ }
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ * Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Autoload;
+
+/**
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
+ *
+ * $loader = new \Composer\Autoload\ClassLoader();
+ *
+ * // register classes with namespaces
+ * $loader->add('Symfony\Component', __DIR__.'/component');
+ * $loader->add('Symfony', __DIR__.'/framework');
+ *
+ * // activate the autoloader
+ * $loader->register();
+ *
+ * // to enable searching the include path (eg. for PEAR packages)
+ * $loader->setUseIncludePath(true);
+ *
+ * In this example, if you try to use a class in the Symfony\Component
+ * namespace or one of its children (Symfony\Component\Console for instance),
+ * the autoloader will first look for the class under the component/
+ * directory, and it will then fallback to the framework/ directory if not
+ * found before giving up.
+ *
+ * This class is loosely based on the Symfony UniversalClassLoader.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ * @see http://www.php-fig.org/psr/psr-0/
+ * @see http://www.php-fig.org/psr/psr-4/
+ */
+class ClassLoader
+{
+ // PSR-4
+ private $prefixLengthsPsr4 = array();
+ private $prefixDirsPsr4 = array();
+ private $fallbackDirsPsr4 = array();
+
+ // PSR-0
+ private $prefixesPsr0 = array();
+ private $fallbackDirsPsr0 = array();
+
+ private $useIncludePath = false;
+ private $classMap = array();
+ private $classMapAuthoritative = false;
+ private $missingClasses = array();
+ private $apcuPrefix;
+
+ public function getPrefixes()
+ {
+ if (!empty($this->prefixesPsr0)) {
+ return call_user_func_array('array_merge', $this->prefixesPsr0);
+ }
+
+ return array();
+ }
+
+ public function getPrefixesPsr4()
+ {
+ return $this->prefixDirsPsr4;
+ }
+
+ public function getFallbackDirs()
+ {
+ return $this->fallbackDirsPsr0;
+ }
+
+ public function getFallbackDirsPsr4()
+ {
+ return $this->fallbackDirsPsr4;
+ }
+
+ public function getClassMap()
+ {
+ return $this->classMap;
+ }
+
+ /**
+ * @param array $classMap Class to filename map
+ */
+ public function addClassMap(array $classMap)
+ {
+ if ($this->classMap) {
+ $this->classMap = array_merge($this->classMap, $classMap);
+ } else {
+ $this->classMap = $classMap;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix, either
+ * appending or prepending to the ones previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param array|string $paths The PSR-0 root directories
+ * @param bool $prepend Whether to prepend the directories
+ */
+ public function add($prefix, $paths, $prepend = false)
+ {
+ if (!$prefix) {
+ if ($prepend) {
+ $this->fallbackDirsPsr0 = array_merge(
+ (array) $paths,
+ $this->fallbackDirsPsr0
+ );
+ } else {
+ $this->fallbackDirsPsr0 = array_merge(
+ $this->fallbackDirsPsr0,
+ (array) $paths
+ );
+ }
+
+ return;
+ }
+
+ $first = $prefix[0];
+ if (!isset($this->prefixesPsr0[$first][$prefix])) {
+ $this->prefixesPsr0[$first][$prefix] = (array) $paths;
+
+ return;
+ }
+ if ($prepend) {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ (array) $paths,
+ $this->prefixesPsr0[$first][$prefix]
+ );
+ } else {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ $this->prefixesPsr0[$first][$prefix],
+ (array) $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace, either
+ * appending or prepending to the ones previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param array|string $paths The PSR-4 base directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function addPsr4($prefix, $paths, $prepend = false)
+ {
+ if (!$prefix) {
+ // Register directories for the root namespace.
+ if ($prepend) {
+ $this->fallbackDirsPsr4 = array_merge(
+ (array) $paths,
+ $this->fallbackDirsPsr4
+ );
+ } else {
+ $this->fallbackDirsPsr4 = array_merge(
+ $this->fallbackDirsPsr4,
+ (array) $paths
+ );
+ }
+ } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
+ // Register directories for a new namespace.
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ } elseif ($prepend) {
+ // Prepend directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ (array) $paths,
+ $this->prefixDirsPsr4[$prefix]
+ );
+ } else {
+ // Append directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ $this->prefixDirsPsr4[$prefix],
+ (array) $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix,
+ * replacing any others previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param array|string $paths The PSR-0 base directories
+ */
+ public function set($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr0 = (array) $paths;
+ } else {
+ $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace,
+ * replacing any others previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param array|string $paths The PSR-4 base directories
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setPsr4($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr4 = (array) $paths;
+ } else {
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Turns on searching the include path for class files.
+ *
+ * @param bool $useIncludePath
+ */
+ public function setUseIncludePath($useIncludePath)
+ {
+ $this->useIncludePath = $useIncludePath;
+ }
+
+ /**
+ * Can be used to check if the autoloader uses the include path to check
+ * for classes.
+ *
+ * @return bool
+ */
+ public function getUseIncludePath()
+ {
+ return $this->useIncludePath;
+ }
+
+ /**
+ * Turns off searching the prefix and fallback directories for classes
+ * that have not been registered with the class map.
+ *
+ * @param bool $classMapAuthoritative
+ */
+ public function setClassMapAuthoritative($classMapAuthoritative)
+ {
+ $this->classMapAuthoritative = $classMapAuthoritative;
+ }
+
+ /**
+ * Should class lookup fail if not found in the current class map?
+ *
+ * @return bool
+ */
+ public function isClassMapAuthoritative()
+ {
+ return $this->classMapAuthoritative;
+ }
+
+ /**
+ * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
+ *
+ * @param string|null $apcuPrefix
+ */
+ public function setApcuPrefix($apcuPrefix)
+ {
+ $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
+ }
+
+ /**
+ * The APCu prefix in use, or null if APCu caching is not enabled.
+ *
+ * @return string|null
+ */
+ public function getApcuPrefix()
+ {
+ return $this->apcuPrefix;
+ }
+
+ /**
+ * Registers this instance as an autoloader.
+ *
+ * @param bool $prepend Whether to prepend the autoloader or not
+ */
+ public function register($prepend = false)
+ {
+ spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+ }
+
+ /**
+ * Unregisters this instance as an autoloader.
+ */
+ public function unregister()
+ {
+ spl_autoload_unregister(array($this, 'loadClass'));
+ }
+
+ /**
+ * Loads the given class or interface.
+ *
+ * @param string $class The name of the class
+ * @return bool|null True if loaded, null otherwise
+ */
+ public function loadClass($class)
+ {
+ if ($file = $this->findFile($class)) {
+ includeFile($file);
+
+ return true;
+ }
+ }
+
+ /**
+ * Finds the path to the file where the class is defined.
+ *
+ * @param string $class The name of the class
+ *
+ * @return string|false The path if found, false otherwise
+ */
+ public function findFile($class)
+ {
+ // class map lookup
+ if (isset($this->classMap[$class])) {
+ return $this->classMap[$class];
+ }
+ if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
+ return false;
+ }
+ if (null !== $this->apcuPrefix) {
+ $file = apcu_fetch($this->apcuPrefix.$class, $hit);
+ if ($hit) {
+ return $file;
+ }
+ }
+
+ $file = $this->findFileWithExtension($class, '.php');
+
+ // Search for Hack files if we are running on HHVM
+ if (false === $file && defined('HHVM_VERSION')) {
+ $file = $this->findFileWithExtension($class, '.hh');
+ }
+
+ if (null !== $this->apcuPrefix) {
+ apcu_add($this->apcuPrefix.$class, $file);
+ }
+
+ if (false === $file) {
+ // Remember that this class does not exist.
+ $this->missingClasses[$class] = true;
+ }
+
+ return $file;
+ }
+
+ private function findFileWithExtension($class, $ext)
+ {
+ // PSR-4 lookup
+ $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
+
+ $first = $class[0];
+ if (isset($this->prefixLengthsPsr4[$first])) {
+ $subPath = $class;
+ while (false !== $lastPos = strrpos($subPath, '\\')) {
+ $subPath = substr($subPath, 0, $lastPos);
+ $search = $subPath . '\\';
+ if (isset($this->prefixDirsPsr4[$search])) {
+ $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
+ foreach ($this->prefixDirsPsr4[$search] as $dir) {
+ if (file_exists($file = $dir . $pathEnd)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-4 fallback dirs
+ foreach ($this->fallbackDirsPsr4 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 lookup
+ if (false !== $pos = strrpos($class, '\\')) {
+ // namespaced class name
+ $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
+ . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
+ } else {
+ // PEAR-like class name
+ $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
+ }
+
+ if (isset($this->prefixesPsr0[$first])) {
+ foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
+ if (0 === strpos($class, $prefix)) {
+ foreach ($dirs as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-0 fallback dirs
+ foreach ($this->fallbackDirsPsr0 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 include paths.
+ if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
+ return $file;
+ }
+
+ return false;
+ }
+}
+
+/**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ */
+function includeFile($file)
+{
+ include $file;
+}
--- /dev/null
+
+Copyright (c) Nils Adermann, Jordi Boggiano
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
--- /dev/null
+<?php
+
+// autoload_classmap.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = $vendorDir;
+
+return array(
+ 'OCA\\ContactsInteraction\\AddressBook' => $baseDir . '/../lib/AddressBook.php',
+ 'OCA\\ContactsInteraction\\AddressBookProvider' => $baseDir . '/../lib/AddressBookProvider.php',
+ 'OCA\\ContactsInteraction\\AppInfo\\Application' => $baseDir . '/../lib/AppInfo/Application.php',
+ 'OCA\\ContactsInteraction\\BackgroundJob\\CleanupJob' => $baseDir . '/../lib/BackgroundJob/CleanupJob.php',
+ 'OCA\\ContactsInteraction\\Card' => $baseDir . '/../lib/Card.php',
+ 'OCA\\ContactsInteraction\\Db\\CardSearchDao' => $baseDir . '/../lib/Db/CardSearchDao.php',
+ 'OCA\\ContactsInteraction\\Db\\RecentContact' => $baseDir . '/../lib/Db/RecentContact.php',
+ 'OCA\\ContactsInteraction\\Db\\RecentContactMapper' => $baseDir . '/../lib/Db/RecentContactMapper.php',
+ 'OCA\\ContactsInteraction\\Listeners\\ContactInteractionListener' => $baseDir . '/../lib/Listeners/ContactInteractionListener.php',
+ 'OCA\\ContactsInteraction\\Migration\\Version010000Date20200304152605' => $baseDir . '/../lib/Migration/Version010000Date20200304152605.php',
+);
--- /dev/null
+<?php
+
+// autoload_namespaces.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = $vendorDir;
+
+return array(
+);
--- /dev/null
+<?php
+
+// autoload_psr4.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = $vendorDir;
+
+return array(
+ 'OCA\\ContactsInteraction\\' => array($baseDir . '/../lib'),
+);
--- /dev/null
+<?php
+
+// autoload_real.php @generated by Composer
+
+class ComposerAutoloaderInitContactsInteraction
+{
+ private static $loader;
+
+ public static function loadClassLoader($class)
+ {
+ if ('Composer\Autoload\ClassLoader' === $class) {
+ require __DIR__ . '/ClassLoader.php';
+ }
+ }
+
+ public static function getLoader()
+ {
+ if (null !== self::$loader) {
+ return self::$loader;
+ }
+
+ spl_autoload_register(array('ComposerAutoloaderInitContactsInteraction', 'loadClassLoader'), true, true);
+ self::$loader = $loader = new \Composer\Autoload\ClassLoader();
+ spl_autoload_unregister(array('ComposerAutoloaderInitContactsInteraction', 'loadClassLoader'));
+
+ $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
+ if ($useStaticLoader) {
+ require_once __DIR__ . '/autoload_static.php';
+
+ call_user_func(\Composer\Autoload\ComposerStaticInitContactsInteraction::getInitializer($loader));
+ } else {
+ $classMap = require __DIR__ . '/autoload_classmap.php';
+ if ($classMap) {
+ $loader->addClassMap($classMap);
+ }
+ }
+
+ $loader->setClassMapAuthoritative(true);
+ $loader->register(true);
+
+ return $loader;
+ }
+}
--- /dev/null
+<?php
+
+// autoload_static.php @generated by Composer
+
+namespace Composer\Autoload;
+
+class ComposerStaticInitContactsInteraction
+{
+ public static $prefixLengthsPsr4 = array (
+ 'O' =>
+ array (
+ 'OCA\\ContactsInteraction\\' => 24,
+ ),
+ );
+
+ public static $prefixDirsPsr4 = array (
+ 'OCA\\ContactsInteraction\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/../lib',
+ ),
+ );
+
+ public static $classMap = array (
+ 'OCA\\ContactsInteraction\\AddressBook' => __DIR__ . '/..' . '/../lib/AddressBook.php',
+ 'OCA\\ContactsInteraction\\AddressBookProvider' => __DIR__ . '/..' . '/../lib/AddressBookProvider.php',
+ 'OCA\\ContactsInteraction\\AppInfo\\Application' => __DIR__ . '/..' . '/../lib/AppInfo/Application.php',
+ 'OCA\\ContactsInteraction\\BackgroundJob\\CleanupJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/CleanupJob.php',
+ 'OCA\\ContactsInteraction\\Card' => __DIR__ . '/..' . '/../lib/Card.php',
+ 'OCA\\ContactsInteraction\\Db\\CardSearchDao' => __DIR__ . '/..' . '/../lib/Db/CardSearchDao.php',
+ 'OCA\\ContactsInteraction\\Db\\RecentContact' => __DIR__ . '/..' . '/../lib/Db/RecentContact.php',
+ 'OCA\\ContactsInteraction\\Db\\RecentContactMapper' => __DIR__ . '/..' . '/../lib/Db/RecentContactMapper.php',
+ 'OCA\\ContactsInteraction\\Listeners\\ContactInteractionListener' => __DIR__ . '/..' . '/../lib/Listeners/ContactInteractionListener.php',
+ 'OCA\\ContactsInteraction\\Migration\\Version010000Date20200304152605' => __DIR__ . '/..' . '/../lib/Migration/Version010000Date20200304152605.php',
+ );
+
+ public static function getInitializer(ClassLoader $loader)
+ {
+ return \Closure::bind(function () use ($loader) {
+ $loader->prefixLengthsPsr4 = ComposerStaticInitContactsInteraction::$prefixLengthsPsr4;
+ $loader->prefixDirsPsr4 = ComposerStaticInitContactsInteraction::$prefixDirsPsr4;
+ $loader->classMap = ComposerStaticInitContactsInteraction::$classMap;
+
+ }, null, ClassLoader::class);
+ }
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @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\ContactsInteraction;
+
+use Exception;
+use OCA\ContactsInteraction\AppInfo\Application;
+use OCA\ContactsInteraction\Db\RecentContact;
+use OCA\ContactsInteraction\Db\RecentContactMapper;
+use OCA\DAV\CardDAV\Integration\ExternalAddressBook;
+use OCA\DAV\DAV\Sharing\Plugin;
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\IL10N;
+use Sabre\DAV\Exception\NotFound;
+use Sabre\DAV\Exception\NotImplemented;
+use Sabre\DAV\PropPatch;
+use Sabre\DAVACL\ACLTrait;
+use Sabre\DAVACL\IACL;
+
+class AddressBook extends ExternalAddressBook implements IACL {
+
+ public const URI = 'recent';
+
+ use ACLTrait;
+
+ /** @var RecentContactMapper */
+ private $mapper;
+
+ /** @var IL10N */
+ private $l10n;
+
+ /** @var string */
+ private $principalUri;
+
+ public function __construct(RecentContactMapper $mapper,
+ IL10N $l10n,
+ string $principalUri) {
+ parent::__construct(Application::APP_ID, self::URI);
+
+ $this->mapper = $mapper;
+ $this->l10n = $l10n;
+ $this->principalUri = $principalUri;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function delete(): void {
+ throw new Exception("This addressbook is immutable");
+ }
+
+ /**
+ * @inheritDoc
+ */
+ function createFile($name, $data = null) {
+ throw new Exception("This addressbook is immutable");
+ }
+
+ /**
+ * @inheritDoc
+ * @throws NotFound
+ */
+ public function getChild($name) {
+ try {
+ return new Card(
+ $this->mapper->find(
+ $this->getUid(),
+ (int)$name
+ ),
+ $this->principalUri,
+ $this->getACL()
+ );
+ } catch (DoesNotExistException $ex) {
+ throw new NotFound("Contact does not exist: " . $ex->getMessage(), 0, $ex);
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getChildren(): array {
+ return array_map(
+ function (RecentContact $contact) {
+ return new Card(
+ $contact,
+ $this->principalUri,
+ $this->getACL()
+ );
+ },
+ $this->mapper->findAll($this->getUid())
+ );
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function childExists($name) {
+ try {
+ $this->mapper->find(
+ $this->getUid(),
+ (int)$name
+ );
+ return true;
+ } catch (DoesNotExistException $e) {
+ return false;
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getLastModified() {
+ throw new NotImplemented();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function propPatch(PropPatch $propPatch) {
+ throw new Exception("This addressbook is immutable");
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getProperties($properties) {
+ return [
+ 'principaluri' => $this->principalUri,
+ '{DAV:}displayname' => $this->l10n->t('Recently contacted'),
+ '{' . Plugin::NS_OWNCLOUD . '}read-only' => true,
+ ];
+ }
+
+ public function getOwner(): string {
+ return $this->principalUri;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getACL() {
+ return [
+ [
+ 'privilege' => '{DAV:}read',
+ 'principal' => $this->getOwner(),
+ 'protected' => true,
+ ],
+ ];
+ }
+
+ private function getUid(): string {
+ list(, $uid) = \Sabre\Uri\split($this->principalUri);
+ return $uid;
+ }
+
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @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\ContactsInteraction;
+
+use OCA\ContactsInteraction\AppInfo\Application;
+use OCA\ContactsInteraction\Db\RecentContactMapper;
+use OCA\DAV\CardDAV\Integration\ExternalAddressBook;
+use OCA\DAV\CardDAV\Integration\IAddressBookProvider;
+use OCP\IL10N;
+
+class AddressBookProvider implements IAddressBookProvider {
+
+ /** @var RecentContactMapper */
+ private $mapper;
+
+ /** @var IL10N */
+ private $l10n;
+
+ public function __construct(RecentContactMapper $mapper, IL10N $l10n) {
+ $this->mapper = $mapper;
+ $this->l10n = $l10n;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getAppId(): string {
+ return Application::APP_ID;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function fetchAllForAddressBookHome(string $principalUri): array {
+ return [
+ new AddressBook($this->mapper, $this->l10n, $principalUri)
+ ];
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function hasAddressBookInAddressBookHome(string $principalUri, string $uri): bool {
+ return $uri === AddressBook::URI;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getAddressBookInAddressBookHome(string $principalUri, string $uri): ?ExternalAddressBook {
+ if ($uri === AddressBook::URI) {
+ return new AddressBook($this->mapper, $this->l10n, $principalUri);
+ }
+
+ return null;
+ }
+
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @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\ContactsInteraction\AppInfo;
+
+use OCA\ContactsInteraction\AddressBook;
+use OCA\ContactsInteraction\Listeners\ContactInteractionListener;
+use OCA\ContactsInteraction\Store;
+use OCP\AppFramework\App;
+use OCP\AppFramework\IAppContainer;
+use OCP\Contacts\Events\ContactInteractedWithEvent;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\EventDispatcher\IEventListener;
+use OCP\IL10N;
+
+class Application extends App {
+
+ public const APP_ID = 'contactsinteraction';
+
+ public function __construct() {
+ parent::__construct(self::APP_ID);
+
+ $this->registerListeners($this->getContainer()->query(IEventDispatcher::class));
+ }
+
+ private function registerListeners(IEventDispatcher $dispatcher): void {
+ $dispatcher->addServiceListener(ContactInteractedWithEvent::class, ContactInteractionListener::class);
+ }
+
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @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\ContactsInteraction\BackgroundJob;
+
+use OCA\ContactsInteraction\Db\RecentContactMapper;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\BackgroundJob\TimedJob;
+
+class CleanupJob extends TimedJob {
+
+ /** @var RecentContactMapper */
+ private $mapper;
+
+ public function __construct(ITimeFactory $time,
+ RecentContactMapper $mapper) {
+ parent::__construct($time);
+
+ $this->setInterval(12 * 60 * 60);
+
+ $this->mapper = $mapper;
+ }
+
+ protected function run($argument) {
+ $time = $this->time->getDateTime();
+ $time->modify('-7days');
+ $this->mapper->cleanUp($time->getTimestamp());
+ }
+
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @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\ContactsInteraction;
+
+use OCA\ContactsInteraction\Db\RecentContact;
+use Sabre\CardDAV\ICard;
+use Sabre\DAV\Exception\NotImplemented;
+use Sabre\DAVACL\ACLTrait;
+use Sabre\DAVACL\IACL;
+
+class Card implements ICard, IACL {
+
+ use ACLTrait;
+
+ /** @var RecentContact */
+ private $contact;
+
+ /** @var string */
+ private $principal;
+
+ /** @var array */
+ private $acls;
+
+ public function __construct(RecentContact $contact, string $principal, array $acls) {
+ $this->contact = $contact;
+ $this->principal = $principal;
+ $this->acls = $acls;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ function getOwner(): ?string {
+ $this->principal;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ function getACL(): array {
+ return $this->acls;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ function setAcls(array $acls): void {
+ throw new NotImplemented();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ function put($data): ?string {
+ throw new NotImplemented();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ function get() {
+ return $this->contact->getCard();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ function getContentType(): ?string {
+ return 'text/vcard; charset=utf-8';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ function getETag(): ?string {
+ return null;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ function getSize(): int {
+ throw new NotImplemented();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ function delete(): void {
+ throw new NotImplemented();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ function getName(): string {
+ return (string) $this->contact->getId();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ function setName($name): void {
+ throw new NotImplemented();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ function getLastModified(): ?int {
+ return $this->contact->getLastContact();
+ }
+
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @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\ContactsInteraction\Db;
+
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IDBConnection;
+use OCP\IUser;
+
+class CardSearchDao {
+
+ /** @var IDBConnection */
+ private $db;
+
+ public function __construct(IDBConnection $db) {
+ $this->db = $db;
+ }
+
+ public function findExisting(IUser $user,
+ ?string $uid,
+ ?string $email,
+ ?string $cloudId): ?string {
+ $addressbooksQuery = $this->db->getQueryBuilder();
+ $cardQuery = $this->db->getQueryBuilder();
+ $propQuery = $this->db->getQueryBuilder();
+
+ $propOr = $propQuery->expr()->orX();
+ if ($uid !== null) {
+ $propOr->add($propQuery->expr()->andX(
+ $propQuery->expr()->eq('name', $cardQuery->createNamedParameter('UID')),
+ $propQuery->expr()->eq('value', $cardQuery->createNamedParameter($uid))
+ ));
+ }
+ if ($email !== null) {
+ $propOr->add($propQuery->expr()->andX(
+ $propQuery->expr()->eq('name', $cardQuery->createNamedParameter('EMAIL')),
+ $propQuery->expr()->eq('value', $cardQuery->createNamedParameter($email))
+ ));
+ }
+ if ($cloudId !== null) {
+ $propOr->add($propQuery->expr()->andX(
+ $propQuery->expr()->eq('name', $cardQuery->createNamedParameter('CLOUD')),
+ $propQuery->expr()->eq('value', $cardQuery->createNamedParameter($cloudId))
+ ));
+ }
+ $addressbooksQuery->selectDistinct('id')
+ ->from('addressbooks')
+ ->where($addressbooksQuery->expr()->eq('principaluri', $cardQuery->createNamedParameter("principals/users/" . $user->getUID())));
+ $propQuery->selectDistinct('cardid')
+ ->from('cards_properties')
+ ->where($propQuery->expr()->in('addressbookid', $propQuery->createFunction($addressbooksQuery->getSQL()), IQueryBuilder::PARAM_INT_ARRAY))
+ ->andWhere($propOr)
+ ->groupBy('cardid');
+ $cardQuery->select('carddata')
+ ->from('cards')
+ ->where($cardQuery->expr()->in('id', $cardQuery->createFunction($propQuery->getSQL()), IQueryBuilder::PARAM_INT_ARRAY))
+ ->andWhere($cardQuery->expr()->in('addressbookid', $cardQuery->createFunction($addressbooksQuery->getSQL()), IQueryBuilder::PARAM_INT_ARRAY))
+ ->setMaxResults(1);
+ $result = $cardQuery->execute();
+ /** @var string|false $card */
+ $card = $result->fetchColumn(0);
+
+ if ($card === false) {
+ return null;
+ }
+
+ return $card;
+ }
+
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @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\ContactsInteraction\Db;
+
+use OCP\AppFramework\Db\Entity;
+
+/**
+ * @method void setActorUid(string $uid)
+ * @method string|null getActorUid()
+ * @method void setUid(string $uid)
+ * @method string|null getUid()
+ * @method void setEmail(string $email)
+ * @method string|null getEmail()
+ * @method void setFederatedCloudId(string $federatedCloudId)
+ * @method string|null getFederatedCloudId()
+ * @method void setCard(string $card)
+ * @method string getCard()
+ * @method void setLastContact(int $lastContact)
+ * @method int getLastContact()
+ */
+class RecentContact extends Entity {
+
+ /** @var string */
+ protected $actorUid;
+
+ /** @var string|null */
+ protected $uid;
+
+ /** @var string|null */
+ protected $email;
+
+ /** @var string|null */
+ protected $federatedCloudId;
+
+ /** @var string */
+ protected $card;
+
+ /** @var int */
+ protected $lastContact;
+
+ public function __construct() {
+ $this->addType('actorUid', 'string');
+ $this->addType('uid', 'string');
+ $this->addType('email', 'string');
+ $this->addType('federatedCloudId', 'string');
+ $this->addType('card', 'string');
+ $this->addType('lastContact', 'int');
+ }
+
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @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\ContactsInteraction\Db;
+
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\AppFramework\Db\QBMapper;
+use OCP\IDBConnection;
+use OCP\IUser;
+
+class RecentContactMapper extends QBMapper {
+
+ public const TABLE_NAME = 'recent_contact';
+
+ public function __construct(IDBConnection $db) {
+ parent::__construct($db, self::TABLE_NAME);
+ }
+
+ /**
+ * @return RecentContact[]
+ */
+ public function findAll(string $uid): array {
+ $qb = $this->db->getQueryBuilder();
+
+ $select = $qb
+ ->select('*')
+ ->from($this->getTableName())
+ ->where($qb->expr()->eq('actor_uid', $qb->createNamedParameter($uid)));
+
+ return $this->findEntities($select);
+ }
+
+ /**
+ * @param string $uid
+ * @param int $id
+ *
+ * @return RecentContact
+ * @throws DoesNotExistException
+ */
+ public function find(string $uid, int $id): RecentContact {
+ $qb = $this->db->getQueryBuilder();
+
+ $select = $qb
+ ->select('*')
+ ->from($this->getTableName())
+ ->where($qb->expr()->eq('id', $qb->createNamedParameter($id, $qb::PARAM_INT)))
+ ->andWhere($qb->expr()->eq('actor_uid', $qb->createNamedParameter($uid)));
+
+ return $this->findEntity($select);
+ }
+
+ /**
+ * @param IUser $user
+ * @param string|null $uid
+ * @param string|null $email
+ * @param string|null $cloudId
+ *
+ * @return RecentContact[]
+ */
+ public function findMatch(IUser $user,
+ ?string $uid,
+ ?string $email,
+ ?string $cloudId): array {
+ $qb = $this->db->getQueryBuilder();
+
+ $or = $qb->expr()->orX();
+ if ($uid !== null) {
+ $or->add($qb->expr()->eq('uid', $qb->createNamedParameter($uid)));
+ }
+ if ($email !== null) {
+ $or->add($qb->expr()->eq('email', $qb->createNamedParameter($email)));
+ }
+ if ($cloudId !== null) {
+ $or->add($qb->expr()->eq('federated_cloud_id', $qb->createNamedParameter($cloudId)));
+ }
+
+ $select = $qb
+ ->select('*')
+ ->from($this->getTableName())
+ ->where($or)
+ ->andWhere($qb->expr()->eq('actor_uid', $qb->createNamedParameter($user->getUID())));
+
+ return $this->findEntities($select);
+ }
+
+ public function cleanUp(int $olderThan): void {
+ $qb = $this->db->getQueryBuilder();
+
+ $delete = $qb
+ ->delete($this->getTableName())
+ ->where($qb->expr()->lt('last_contact', $qb->createNamedParameter($olderThan)));
+
+ $delete->execute();
+ }
+
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @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\ContactsInteraction\Listeners;
+
+use OCA\ContactsInteraction\Db\CardSearchDao;
+use OCA\ContactsInteraction\Db\RecentContact;
+use OCA\ContactsInteraction\Db\RecentContactMapper;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\Contacts\Events\ContactInteractedWithEvent;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventListener;
+use OCP\IL10N;
+use OCP\ILogger;
+use OCP\IUserManager;
+use Sabre\VObject\Component\VCard;
+use Sabre\VObject\Reader;
+use Sabre\VObject\UUIDUtil;
+use Throwable;
+
+class ContactInteractionListener implements IEventListener {
+
+ /** @var RecentContactMapper */
+ private $mapper;
+
+ /** @var CardSearchDao */
+ private $cardSearchDao;
+
+ /** @var IUserManager */
+ private $userManager;
+
+ /** @var ITimeFactory */
+ private $timeFactory;
+
+ /** @var IL10N */
+ private $l10n;
+
+ /** @var ILogger */
+ private $logger;
+
+ public function __construct(RecentContactMapper $mapper,
+ CardSearchDao $cardSearchDao,
+ IUserManager $userManager,
+ ITimeFactory $timeFactory,
+ IL10N $l10nFactory,
+ ILogger $logger) {
+ $this->mapper = $mapper;
+ $this->cardSearchDao = $cardSearchDao;
+ $this->userManager = $userManager;
+ $this->timeFactory = $timeFactory;
+ $this->l10n = $l10nFactory;
+ $this->logger = $logger;
+ }
+
+ public function handle(Event $event): void {
+ if (!($event instanceof ContactInteractedWithEvent)) {
+ return;
+ }
+
+ if ($event->getUid() === null && $event->getEmail() === null && $event->getFederatedCloudId() === null) {
+ $this->logger->warning("Contact interaction event has no user identifier set");
+ return;
+ }
+
+ $existing = $this->mapper->findMatch(
+ $event->getActor(),
+ $event->getUid(),
+ $event->getEmail(),
+ $event->getFederatedCloudId()
+ );
+ if (!empty($existing)) {
+ $now = $this->timeFactory->getTime();
+ foreach ($existing as $c) {
+ $c->setLastContact($now);
+ $this->mapper->update($c);
+ }
+
+ return;
+ }
+
+ $contact = new RecentContact();
+ $contact->setActorUid($event->getActor()->getUID());
+ if ($event->getUid() !== null) {
+ $contact->setUid($event->getUid());
+ }
+ if ($event->getEmail() !== null) {
+ $contact->setEmail($event->getEmail());
+ }
+ if ($event->getFederatedCloudId() !== null) {
+ $contact->setFederatedCloudId($event->getFederatedCloudId());
+ }
+ $contact->setLastContact($this->timeFactory->getTime());
+
+ $copy = $this->cardSearchDao->findExisting(
+ $event->getActor(),
+ $event->getUid(),
+ $event->getEmail(),
+ $event->getFederatedCloudId()
+ );
+ if ($copy !== null) {
+ try {
+ $parsed = Reader::read($copy, Reader::OPTION_FORGIVING);
+ $parsed->CATEGORIES = $this->l10n->t('Recently contacted');
+ $contact->setCard($parsed->serialize());
+ } catch (Throwable $e) {
+ $this->logger->logException($e, [
+ 'message' => 'Could not parse card to add recent category: ' . $e->getMessage(),
+ 'level' => ILogger::WARN,
+ ]);
+ $contact->setCard($copy);
+ }
+ } else {
+ $contact->setCard($this->generateCard($contact));
+ }
+ $this->mapper->insert($contact);
+ }
+
+ private function getDisplayName(?string $uid): ?string {
+ if ($uid === null) {
+ return null;
+ }
+ if (($user = $this->userManager->get($uid)) === null) {
+ return null;
+ }
+
+ return $user->getDisplayName();
+ }
+
+ private function generateCard(RecentContact $contact): string {
+ $props = [
+ 'URI' => UUIDUtil::getUUID(),
+ 'FN' => $this->getDisplayName($contact->getUid()) ?? $contact->getEmail() ?? $contact->getFederatedCloudId(),
+ 'CATEGORIES' => $this->l10n->t('Recently contacted'),
+ ];
+
+ if ($contact->getUid() !== null) {
+ $props['X-NEXTCLOUD-UID'] = $contact->getUid();
+ }
+ if ($contact->getEmail() !== null) {
+ $props['EMAIL'] = $contact->getEmail();
+ }
+ if ($contact->getFederatedCloudId() !== null) {
+ $props['CLOUD'] = $contact->getFederatedCloudId();
+ }
+
+ return (new VCard($props))->serialize();
+ }
+
+}
--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @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\ContactsInteraction\Migration;
+
+use Closure;
+use OCA\ContactsInteraction\Db\RecentContactMapper;
+use OCP\DB\ISchemaWrapper;
+use OCP\Migration\IOutput;
+use OCP\Migration\SimpleMigrationStep;
+
+class Version010000Date20200304152605 extends SimpleMigrationStep {
+
+ /**
+ * @param IOutput $output
+ * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
+ * @param array $options
+ *
+ * @return ISchemaWrapper
+ */
+ public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ISchemaWrapper {
+ /** @var ISchemaWrapper $schema */
+ $schema = $schemaClosure();
+
+ $table = $schema->createTable(RecentContactMapper::TABLE_NAME);
+ $table->addColumn('id', 'integer', [
+ 'autoincrement' => true,
+ 'notnull' => true,
+ 'length' => 4,
+ ]);
+ $table->addColumn('actor_uid', 'string', [
+ 'notnull' => true,
+ 'length' => 64,
+ ]);
+ $table->addColumn('uid', 'string', [
+ 'notnull' => false,
+ 'length' => 64,
+ ]);
+ $table->addColumn('email', 'string', [
+ 'notnull' => false,
+ 'length' => 255,
+ ]);
+ $table->addColumn('federated_cloud_id', 'string', [
+ 'notnull' => false,
+ 'length' => 255,
+ ]);
+ $table->addColumn('card', 'blob', [
+ 'notnull' => true,
+ ]);
+ $table->addColumn('last_contact', 'integer', [
+ 'notnull' => true,
+ 'length' => 4,
+ ]);
+ $table->setPrimaryKey(['id']);
+ // To find all recent entries
+ $table->addIndex(['actor_uid'], RecentContactMapper::TABLE_NAME . '_actor_uid');
+ // To find a specific entry
+ $table->addIndex(['id', 'actor_uid'], RecentContactMapper::TABLE_NAME . '_id_uid');
+ // To find all recent entries with a given UID
+ $table->addIndex(['uid'], RecentContactMapper::TABLE_NAME . '_uid');
+ // To find all recent entries with a given email address
+ $table->addIndex(['email'], RecentContactMapper::TABLE_NAME . '_email');
+ // To find all recent entries with a give federated cloud id
+ $table->addIndex(['federated_cloud_id'], RecentContactMapper::TABLE_NAME . '_fed_id');
+ // For the cleanup
+ $table->addIndex(['last_contact'], RecentContactMapper::TABLE_NAME . '_last_contact');
+
+ return $schema;
+ }
+
+}
namespace OCA\DAV\CardDAV;
use OCA\DAV\AppInfo\PluginManager;
+use OCA\DAV\CardDAV\Integration\IAddressBookProvider;
use OCA\DAV\CardDAV\Integration\ExternalAddressBook;
use OCP\IConfig;
use OCP\IL10N;
use Sabre\CardDAV\Backend;
use Sabre\DAV\Exception\MethodNotAllowed;
+use Sabre\CardDAV\IAddressBook;
+use function array_map;
use Sabre\DAV\MkCol;
class UserAddressBooks extends \Sabre\CardDAV\AddressBookHome {
/**
* Returns a list of address books
*
- * @return array
+ * @return IAddressBook[]
*/
function getChildren() {
if ($this->l10n === null) {
}
$addressBooks = $this->carddavBackend->getAddressBooksForUser($this->principalUri);
+ /** @var IAddressBook[] $objects */
$objects = array_map(function(array $addressBook) {
if ($addressBook['principaluri'] === 'principals/system/system') {
return new SystemAddressbook($this->carddavBackend, $addressBook, $this->l10n, $this->config);
return new AddressBook($this->carddavBackend, $addressBook, $this->l10n);
}, $addressBooks);
- foreach ($this->pluginManager->getAddressBookPlugins() as $plugin) {
- $plugin->fetchAllForAddressBookHome($this->principalUri);
- }
- return $objects;
+ /** @var IAddressBook[][] $objectsFromPlugins */
+ $objectsFromPlugins = array_map(function(IAddressBookProvider $plugin): array {
+ return $plugin->fetchAllForAddressBookHome($this->principalUri);
+ }, $this->pluginManager->getAddressBookPlugins());
+ return array_merge($objects, ...$objectsFromPlugins);
}
public function createExtendedCollection($name, MkCol $mkCol) {
} else {
$shippedFilter = null;
}
-
+
$apps = \OC_App::getAllApps();
$enabledApps = $disabledApps = [];
$versions = \OC_App::getAppVersions();
"admin_audit",
"cloud_federation_api",
"comments",
+ "contactsinteraction",
"dav",
"encryption",
"federatedfilesharing",