diff options
author | Georg Ehrke <developer@georgehrke.com> | 2017-03-25 11:56:40 +0100 |
---|---|---|
committer | Georg Ehrke <developer@georgehrke.com> | 2017-04-24 22:38:20 +0200 |
commit | 57b543a9188f84031d81713117f3df515ab13e84 (patch) | |
tree | e7770185645c5d46609ccc10671250db531817ce /apps/dav/lib/CalDAV/CalDavBackend.php | |
parent | d01832ff64652f3e52fa41cccb19f122701af2c8 (diff) | |
download | nextcloud-server-57b543a9188f84031d81713117f3df515ab13e84.tar.gz nextcloud-server-57b543a9188f84031d81713117f3df515ab13e84.zip |
add Nextcloud Search extension to CalDAV
Signed-off-by: Georg Ehrke <developer@georgehrke.com>
Diffstat (limited to 'apps/dav/lib/CalDAV/CalDavBackend.php')
-rw-r--r-- | apps/dav/lib/CalDAV/CalDavBackend.php | 278 |
1 files changed, 278 insertions, 0 deletions
diff --git a/apps/dav/lib/CalDAV/CalDavBackend.php b/apps/dav/lib/CalDAV/CalDavBackend.php index a4fed4f1982..c59a2dcd147 100644 --- a/apps/dav/lib/CalDAV/CalDavBackend.php +++ b/apps/dav/lib/CalDAV/CalDavBackend.php @@ -44,6 +44,7 @@ use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\PropPatch; use Sabre\HTTP\URLUtil; +use Sabre\VObject\Component\VCalendar; use Sabre\VObject\DateTimeParser; use Sabre\VObject\Reader; use Sabre\VObject\Recur\EventIterator; @@ -108,6 +109,17 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription '{http://calendarserver.org/ns/}subscribed-strip-attachments' => 'stripattachments', ]; + /** @var array properties to index */ + public static $indexProperties = ['CATEGORIES', 'COMMENT', 'DESCRIPTION', + 'LOCATION', 'RESOURCES', 'STATUS', 'SUMMARY', 'ATTENDEE', 'CONTACT', + 'ORGANIZER']; + + /** @var array parameters to index */ + public static $indexParameters = [ + 'ATTENDEE' => ['CN'], + 'ORGANIZER' => ['CN'], + ]; + /** * @var string[] Map of uid => display name */ @@ -134,6 +146,9 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription /** @var bool */ private $legacyEndpoint; + /** @var string */ + private $dbObjectPropertiesTable = 'calendarobjects_properties'; + /** * CalDavBackend constructor. * @@ -746,6 +761,11 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription $stmt->execute([$calendarId]); $this->sharingBackend->deleteAllShares($calendarId); + + $query = $this->db->getQueryBuilder(); + $query->delete($this->dbObjectPropertiesTable) + ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId))) + ->execute(); } /** @@ -940,6 +960,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription ]) ->execute(); + $this->updateProperties($calendarId, $objectUri, $calendarData); + $this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject', new GenericEvent( '\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject', [ @@ -990,6 +1012,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri))) ->execute(); + $this->updateProperties($calendarId, $objectUri, $calendarData); + $data = $this->getCalendarObject($calendarId, $objectUri); if (is_array($data)) { $this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject', new GenericEvent( @@ -1050,6 +1074,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `uri` = ?'); $stmt->execute([$calendarId, $objectUri]); + $this->purgeProperties($calendarId, $data['id']); + $this->addChange($calendarId, $objectUri, 3); } @@ -1168,6 +1194,139 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription } /** + * custom Nextcloud search extension for CalDAV + * + * @param string $principalUri + * @param array $filters + * @param integer|null $limit + * @param integer|null $offset + * @return array + */ + public function calendarSearch($principalUri, array $filters, $limit=null, $offset=null) { + $calendars = $this->getCalendarsForUser($principalUri); + $ownCalendars = []; + $sharedCalendars = []; + + $uriMapper = []; + + foreach($calendars as $calendar) { + if ($calendar['{http://owncloud.org/ns}owner-principal'] === $principalUri) { + $ownCalendars[] = $calendar['id']; + } else { + $sharedCalendars[] = $calendar['id']; + } + $uriMapper[$calendar['id']] = $calendar['uri']; + } + if (count($ownCalendars) === 0 && count($sharedCalendars) === 0) { + return []; + } + + $query = $this->db->getQueryBuilder(); + // Calendar id expressions + $calendarExpressions = []; + foreach($ownCalendars as $id) { + $calendarExpressions[] = $query->expr() + ->eq('c.calendarid', $query->createNamedParameter($id)); + } + foreach($sharedCalendars as $id) { + $calendarExpressions[] = $query->expr()->andX( + $query->expr()->eq('c.calendarid', + $query->createNamedParameter($id)), + $query->expr()->eq('c.classification', + $query->createNamedParameter(self::CLASSIFICATION_PUBLIC)) + ); + } + + if (count($calendarExpressions) === 1) { + $calExpr = $calendarExpressions[0]; + } else { + $calExpr = call_user_func_array([$query->expr(), 'orX'], $calendarExpressions); + } + + // Component expressions + $compExpressions = []; + foreach($filters['comps'] as $comp) { + $compExpressions[] = $query->expr() + ->eq('c.componenttype', $query->createNamedParameter($comp)); + } + + if (count($compExpressions) === 1) { + $compExpr = $compExpressions[0]; + } else { + $compExpr = call_user_func_array([$query->expr(), 'orX'], $compExpressions); + } + + $propExpressions = []; + foreach($filters['props'] as $prop) { + $propExpressions[] = $query->expr()->andX( + $query->expr()->eq('i.name', $query->createNamedParameter($prop)), + $query->expr()->isNull('i.parameter') + ); + } + + if (count($propExpressions) === 1) { + $propExpr = $propExpressions[0]; + } else { + $propExpr = call_user_func_array([$query->expr(), 'orX'], $propExpressions); + } + + $paramExpressions = []; + foreach($filters['params'] as $param) { + $paramExpressions[] = $query->expr()->andX( + $query->expr()->eq('i.name', $query->createNamedParameter($param['property'])), + $query->expr()->eq('i.parameter', $query->createNamedParameter($param['parameter'])) + ); + } + + if (count($paramExpressions) === 1) { + $paramExpr = $paramExpressions[0]; + } else { + $paramExpr = call_user_func_array([$query->expr(), 'orX'], $paramExpressions); + } + + $offset = 0; + + $query->select(['c.calendarid', 'c.uri']) + ->from('calendarobjects_properties', 'i') + ->join('i', 'calendarobjects', 'c', $query->expr()->eq('i.objectid', 'c.id')) + ->where($calExpr) + ->andWhere($compExpr) + ->andWhere($query->expr()->orX($propExpr, $paramExpr)) + ->andWhere($query->expr()->like('i.value', $query->createNamedParameter($filters['search-term']))) + ->setFirstResult($offset) + ->setMaxResults($limit); + + $stmt = $query->execute(); + + $result = []; + while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + $result[] = $uriMapper[$row['calendarid']] . '/' . $row['uri']; + } + + return $result; + } + + /** + * This method validates if a filter (as passed to calendarSearch) matches + * the given object. + * + * @param array $object + * @param array $filters + * @return bool + */ + protected function validateFilterForCalendarSearch(array $object, array $filters) { + $vObject = Reader::read($object['calendardata']); + + $validator = new Search\CalendarSearchValidator(); + $result = $validator->validate($vObject, $filters); + + // Destroy circular references so PHP will GC the object. + $vObject->destroy(); + + return $result; + } + + /** * Searches through all of a users calendars and calendar objects to find * an object with a specific UID. * @@ -1820,6 +1979,125 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription return $this->sharingBackend->applyShareAcl($resourceId, $acl); } + + + /** + * update properties table + * + * @param int $calendarId + * @param string $objectUri + * @param string $calendarData + */ + protected function updateProperties($calendarId, $objectUri, $calendarData) { + $objectId = $this->getCalendarObjectId($calendarId, $objectUri); + $vCalendar = $this->readCalendarData($calendarData); + + $this->purgeProperties($calendarId, $objectId); + + $query = $this->db->getQueryBuilder(); + $query->insert($this->dbObjectPropertiesTable) + ->values( + [ + 'calendarid' => $query->createNamedParameter($calendarId), + 'objectid' => $query->createNamedParameter($objectId), + 'name' => $query->createParameter('name'), + 'parameter' => $query->createParameter('parameter'), + 'value' => $query->createParameter('value'), + ] + ); + + $indexComponents = ['VEVENT', 'VJOURNAL', 'VTODO']; + foreach ($vCalendar->getComponents() as $component) { + if (!in_array($component->name, $indexComponents)) { + continue; + } + + foreach ($component->children() as $property) { + if (in_array($property->name, self::$indexProperties)) { + $value = $property->getValue(); + // is this a shitty db? + if ($this->db->supports4ByteText()) { + $value = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $value); + } + $value = substr($value, 0, 254); + + $query->setParameter('name', $property->name); + $query->setParameter('parameter', null); + $query->setParameter('value', $value); + $query->execute(); + } + + if (in_array($property->name, array_keys(self::$indexParameters))) { + $parameters = $property->parameters(); + $indexedParametersForProperty = self::$indexParameters[$property->name]; + + foreach ($parameters as $key => $value) { + if (in_array($key, $indexedParametersForProperty)) { + // is this a shitty db? + if ($this->db->supports4ByteText()) { + $value = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $value); + } + $value = substr($value, 0, 254); + + $query->setParameter('name', $property->name); + $query->setParameter('parameter', substr($key, 0, 254)); + $query->setParameter('value', substr($value, 0, 254)); + $query->execute(); + } + } + } + } + } + } + + /** + * read VCalendar data into a VCalendar object + * + * @param string $objectData + * @return VCalendar + */ + protected function readCalendarData($objectData) { + return Reader::read($objectData); + } + + /** + * delete all properties from a given calendar object + * + * @param int $calendarId + * @param int $objectId + */ + protected function purgeProperties($calendarId, $objectId) { + $query = $this->db->getQueryBuilder(); + $query->delete($this->dbObjectPropertiesTable) + ->where($query->expr()->eq('objectid', $query->createNamedParameter($objectId))) + ->andWhere($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId))); + $query->execute(); + } + + /** + * get ID from a given calendar object + * + * @param int $calendarId + * @param string $uri + * @return int + */ + protected function getCalendarObjectId($calendarId, $uri) { + $query = $this->db->getQueryBuilder(); + $query->select('id')->from('calendarobjects') + ->where($query->expr()->eq('uri', $query->createNamedParameter($uri))) + ->andWhere($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId))); + + $result = $query->execute(); + $objectIds = $result->fetch(); + $result->closeCursor(); + + if (!isset($objectIds['id'])) { + throw new \InvalidArgumentException('Calendarobject does not exists: ' . $uri); + } + + return (int)$objectIds['id']; + } + private function convertPrincipal($principalUri, $toV2) { if ($this->principalBackend->getPrincipalPrefix() === 'principals') { list(, $name) = URLUtil::splitPath($principalUri); |