2015-10-31 03:28:21 +03:00
< ? php
/**
2016-07-21 17:49:16 +03:00
* @ copyright Copyright ( c ) 2016 , ownCloud , Inc .
2017-04-25 20:26:47 +03:00
* @ copyright Copyright ( c ) 2017 Georg Ehrke
2016-07-21 17:49:16 +03:00
*
* @ author Joas Schilling < coding @ schilljs . com >
2016-05-26 20:56:05 +03:00
* @ author Stefan Weil < sw @ weilnetz . de >
2016-07-21 17:49:16 +03:00
* @ author Thomas Citharel < tcit @ tcit . fr >
2015-10-31 03:28:21 +03:00
* @ author Thomas Müller < thomas . mueller @ tmit . eu >
2017-04-25 20:26:47 +03:00
* @ author Georg Ehrke < oc . list @ georgehrke . com >
2015-10-31 03:28:21 +03:00
*
* @ license AGPL - 3.0
*
* This code is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License , version 3 ,
* as published by the Free Software Foundation .
*
* 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 , version 3 ,
* along with this program . If not , see < http :// www . gnu . org / licenses />
*
*/
namespace OCA\DAV\CalDAV ;
2016-01-25 19:18:47 +03:00
use OCA\DAV\DAV\Sharing\IShareable ;
2016-01-25 19:17:36 +03:00
use OCP\DB\QueryBuilder\IQueryBuilder ;
2016-01-26 14:06:02 +03:00
use OCA\DAV\Connector\Sabre\Principal ;
use OCA\DAV\DAV\Sharing\Backend ;
2016-01-25 19:18:47 +03:00
use OCP\IDBConnection ;
2016-08-18 16:10:18 +03:00
use OCP\IUser ;
use OCP\IUserManager ;
2016-09-03 11:52:05 +03:00
use OCP\Security\ISecureRandom ;
2015-10-31 03:28:21 +03:00
use Sabre\CalDAV\Backend\AbstractBackend ;
use Sabre\CalDAV\Backend\SchedulingSupport ;
use Sabre\CalDAV\Backend\SubscriptionSupport ;
use Sabre\CalDAV\Backend\SyncSupport ;
2015-11-20 15:35:23 +03:00
use Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp ;
use Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet ;
2015-10-31 03:28:21 +03:00
use Sabre\DAV ;
use Sabre\DAV\Exception\Forbidden ;
2016-08-01 16:07:22 +03:00
use Sabre\DAV\Exception\NotFound ;
2016-04-19 12:33:37 +03:00
use Sabre\DAV\PropPatch ;
2016-01-26 14:06:02 +03:00
use Sabre\HTTP\URLUtil ;
2017-03-25 13:56:40 +03:00
use Sabre\VObject\Component\VCalendar ;
2015-10-31 03:28:21 +03:00
use Sabre\VObject\DateTimeParser ;
use Sabre\VObject\Reader ;
2016-04-19 12:33:37 +03:00
use Sabre\VObject\Recur\EventIterator ;
2016-10-13 16:34:26 +03:00
use Symfony\Component\EventDispatcher\EventDispatcherInterface ;
use Symfony\Component\EventDispatcher\GenericEvent ;
2015-10-31 03:28:21 +03:00
/**
* Class CalDavBackend
*
* Code is heavily inspired by https :// github . com / fruux / sabre - dav / blob / master / lib / CalDAV / Backend / PDO . php
*
* @ package OCA\DAV\CalDAV
*/
class CalDavBackend extends AbstractBackend implements SyncSupport , SubscriptionSupport , SchedulingSupport {
2016-09-20 15:09:08 +03:00
const PERSONAL_CALENDAR_URI = 'personal' ;
const PERSONAL_CALENDAR_NAME = 'Personal' ;
2015-10-31 03:28:21 +03:00
/**
* We need to specify a max date , because we need to stop * somewhere *
*
* On 32 bit system the maximum for a signed integer is 2147483647 , so
* MAX_DATE cannot be higher than date ( 'Y-m-d' , 2147483647 ) which results
* in 2038 - 01 - 19 to avoid problems when the date is converted
* to a unix timestamp .
*/
const MAX_DATE = '2038-01-01' ;
2016-07-06 13:39:07 +03:00
const ACCESS_PUBLIC = 4 ;
2016-04-19 12:33:37 +03:00
const CLASSIFICATION_PUBLIC = 0 ;
const CLASSIFICATION_PRIVATE = 1 ;
const CLASSIFICATION_CONFIDENTIAL = 2 ;
2015-10-31 03:28:21 +03:00
/**
2016-01-25 19:18:47 +03:00
* List of CalDAV properties , and how they map to database field names
2015-10-31 03:28:21 +03:00
* Add your own properties by simply adding on to this array .
*
* Note that only string - based properties are supported here .
*
* @ var array
*/
public $propertyMap = [
'{DAV:}displayname' => 'displayname' ,
'{urn:ietf:params:xml:ns:caldav}calendar-description' => 'description' ,
'{urn:ietf:params:xml:ns:caldav}calendar-timezone' => 'timezone' ,
'{http://apple.com/ns/ical/}calendar-order' => 'calendarorder' ,
'{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor' ,
];
/**
2016-01-25 19:18:47 +03:00
* List of subscription properties , and how they map to database field names .
2015-10-31 03:28:21 +03:00
*
* @ var array
*/
public $subscriptionPropertyMap = [
'{DAV:}displayname' => 'displayname' ,
'{http://apple.com/ns/ical/}refreshrate' => 'refreshrate' ,
'{http://apple.com/ns/ical/}calendar-order' => 'calendarorder' ,
'{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor' ,
'{http://calendarserver.org/ns/}subscribed-strip-todos' => 'striptodos' ,
'{http://calendarserver.org/ns/}subscribed-strip-alarms' => 'stripalarms' ,
'{http://calendarserver.org/ns/}subscribed-strip-attachments' => 'stripattachments' ,
];
2017-03-25 13:56:40 +03:00
/** @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' ],
];
2016-08-18 16:10:18 +03:00
/**
* @ var string [] Map of uid => display name
*/
protected $userDisplayNames ;
2016-01-25 19:18:47 +03:00
/** @var IDBConnection */
2016-01-26 14:06:02 +03:00
private $db ;
/** @var Backend */
private $sharingBackend ;
/** @var Principal */
private $principalBackend ;
2016-08-18 16:10:18 +03:00
/** @var IUserManager */
private $userManager ;
2016-09-03 11:52:05 +03:00
/** @var ISecureRandom */
private $random ;
2016-10-13 16:34:26 +03:00
/** @var EventDispatcherInterface */
private $dispatcher ;
2016-10-13 12:04:22 +03:00
2016-12-15 18:59:46 +03:00
/** @var bool */
private $legacyEndpoint ;
2017-03-25 13:56:40 +03:00
/** @var string */
2017-04-26 11:06:10 +03:00
private $dbObjectPropertiesTable = 'calendarobjects_props' ;
2017-03-25 13:56:40 +03:00
2016-01-26 14:06:02 +03:00
/**
* CalDavBackend constructor .
*
2016-01-25 19:18:47 +03:00
* @ param IDBConnection $db
* @ param Principal $principalBackend
2016-08-18 16:10:18 +03:00
* @ param IUserManager $userManager
2016-09-03 11:52:05 +03:00
* @ param ISecureRandom $random
2016-10-13 16:34:26 +03:00
* @ param EventDispatcherInterface $dispatcher
2016-12-15 18:59:46 +03:00
* @ param bool $legacyEndpoint
2016-01-26 14:06:02 +03:00
*/
2016-08-30 20:21:32 +03:00
public function __construct ( IDBConnection $db ,
Principal $principalBackend ,
IUserManager $userManager ,
2016-10-13 12:04:22 +03:00
ISecureRandom $random ,
2016-12-15 18:59:46 +03:00
EventDispatcherInterface $dispatcher ,
$legacyEndpoint = false ) {
2015-10-31 03:28:21 +03:00
$this -> db = $db ;
2016-01-26 14:06:02 +03:00
$this -> principalBackend = $principalBackend ;
2016-08-18 16:10:18 +03:00
$this -> userManager = $userManager ;
2016-02-05 19:30:16 +03:00
$this -> sharingBackend = new Backend ( $this -> db , $principalBackend , 'calendar' );
2016-09-03 11:52:05 +03:00
$this -> random = $random ;
2016-10-13 16:34:26 +03:00
$this -> dispatcher = $dispatcher ;
2016-12-15 18:59:46 +03:00
$this -> legacyEndpoint = $legacyEndpoint ;
2015-10-31 03:28:21 +03:00
}
2016-08-30 16:11:33 +03:00
/**
* Return the number of calendars for a principal
*
* By default this excludes the automatically generated birthday calendar
*
* @ param $principalUri
* @ param bool $excludeBirthday
* @ return int
*/
public function getCalendarsForUserCount ( $principalUri , $excludeBirthday = true ) {
$principalUri = $this -> convertPrincipal ( $principalUri , true );
$query = $this -> db -> getQueryBuilder ();
$query -> select ( $query -> createFunction ( 'COUNT(*)' ))
-> from ( 'calendars' )
-> where ( $query -> expr () -> eq ( 'principaluri' , $query -> createNamedParameter ( $principalUri )));
if ( $excludeBirthday ) {
$query -> andWhere ( $query -> expr () -> neq ( 'uri' , $query -> createNamedParameter ( BirthdayService :: BIRTHDAY_CALENDAR_URI )));
}
2016-09-09 20:15:27 +03:00
return ( int ) $query -> execute () -> fetchColumn ();
2016-08-30 16:11:33 +03:00
}
2015-10-31 03:28:21 +03:00
/**
* Returns a list of calendars for a principal .
*
* Every project is an array with the following keys :
* * id , a unique id that will be used by other functions to modify the
* calendar . This can be the same as the uri or a database key .
* * uri , which the basename of the uri with which the calendar is
* accessed .
* * principaluri . The owner of the calendar . Almost always the same as
* principalUri passed to this method .
*
* Furthermore it can contain webdav properties in clark notation . A very
* common one is '{DAV:}displayname' .
*
* Many clients also require :
* { urn : ietf : params : xml : ns : caldav } supported - calendar - component - set
* For this property , you can just return an instance of
* Sabre\CalDAV\Property\SupportedCalendarComponentSet .
*
* If you return { http :// sabredav . org / ns } read - only and set the value to 1 ,
* ACL will automatically be put in read - only mode .
*
* @ param string $principalUri
* @ return array
*/
function getCalendarsForUser ( $principalUri ) {
2016-03-17 12:31:33 +03:00
$principalUriOriginal = $principalUri ;
2016-02-12 16:38:43 +03:00
$principalUri = $this -> convertPrincipal ( $principalUri , true );
2015-10-31 03:28:21 +03:00
$fields = array_values ( $this -> propertyMap );
$fields [] = 'id' ;
$fields [] = 'uri' ;
$fields [] = 'synctoken' ;
$fields [] = 'components' ;
$fields [] = 'principaluri' ;
$fields [] = 'transparent' ;
// Making fields a comma-delimited list
$query = $this -> db -> getQueryBuilder ();
$query -> select ( $fields ) -> from ( 'calendars' )
-> where ( $query -> expr () -> eq ( 'principaluri' , $query -> createNamedParameter ( $principalUri )))
-> orderBy ( 'calendarorder' , 'ASC' );
$stmt = $query -> execute ();
$calendars = [];
while ( $row = $stmt -> fetch ( \PDO :: FETCH_ASSOC )) {
$components = [];
if ( $row [ 'components' ]) {
$components = explode ( ',' , $row [ 'components' ]);
}
$calendar = [
'id' => $row [ 'id' ],
'uri' => $row [ 'uri' ],
2016-12-15 18:59:46 +03:00
'principaluri' => $this -> convertPrincipal ( $row [ 'principaluri' ], ! $this -> legacyEndpoint ),
2015-10-31 03:28:21 +03:00
'{' . Plugin :: NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ( $row [ 'synctoken' ] ? $row [ 'synctoken' ] : '0' ),
'{http://sabredav.org/ns}sync-token' => $row [ 'synctoken' ] ? $row [ 'synctoken' ] : '0' ,
'{' . Plugin :: NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet ( $components ),
'{' . Plugin :: NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp ( $row [ 'transparent' ] ? 'transparent' : 'opaque' ),
2016-12-15 18:59:46 +03:00
'{' . \OCA\DAV\DAV\Sharing\Plugin :: NS_OWNCLOUD . '}owner-principal' => $this -> convertPrincipal ( $principalUri , ! $this -> legacyEndpoint ),
2015-10-31 03:28:21 +03:00
];
foreach ( $this -> propertyMap as $xmlName => $dbName ) {
$calendar [ $xmlName ] = $row [ $dbName ];
}
2017-04-19 17:18:44 +03:00
$this -> addOwnerPrincipal ( $calendar );
2016-02-05 19:25:07 +03:00
if ( ! isset ( $calendars [ $calendar [ 'id' ]])) {
$calendars [ $calendar [ 'id' ]] = $calendar ;
}
2015-10-31 03:28:21 +03:00
}
2016-01-26 14:06:02 +03:00
$stmt -> closeCursor ();
// query for shared calendars
2016-03-17 12:31:33 +03:00
$principals = $this -> principalBackend -> getGroupMembership ( $principalUriOriginal , true );
2016-01-26 14:06:02 +03:00
$principals [] = $principalUri ;
$fields = array_values ( $this -> propertyMap );
$fields [] = 'a.id' ;
$fields [] = 'a.uri' ;
$fields [] = 'a.synctoken' ;
$fields [] = 'a.components' ;
$fields [] = 'a.principaluri' ;
$fields [] = 'a.transparent' ;
2016-03-17 17:39:08 +03:00
$fields [] = 's.access' ;
2016-01-26 14:06:02 +03:00
$query = $this -> db -> getQueryBuilder ();
$result = $query -> select ( $fields )
-> from ( 'dav_shares' , 's' )
-> join ( 's' , 'calendars' , 'a' , $query -> expr () -> eq ( 's.resourceid' , 'a.id' ))
-> where ( $query -> expr () -> in ( 's.principaluri' , $query -> createParameter ( 'principaluri' )))
-> andWhere ( $query -> expr () -> eq ( 's.type' , $query -> createParameter ( 'type' )))
-> setParameter ( 'type' , 'calendar' )
-> setParameter ( 'principaluri' , $principals , \Doctrine\DBAL\Connection :: PARAM_STR_ARRAY )
-> execute ();
2017-03-02 13:34:27 +03:00
$readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin :: NS_OWNCLOUD . '}read-only' ;
2016-01-26 14:06:02 +03:00
while ( $row = $result -> fetch ()) {
2017-03-26 01:07:09 +03:00
if ( $row [ 'principaluri' ] === $principalUri ) {
continue ;
}
2017-03-02 13:34:27 +03:00
$readOnly = ( int ) $row [ 'access' ] === Backend :: ACCESS_READ ;
2017-03-02 13:24:56 +03:00
if ( isset ( $calendars [ $row [ 'id' ]])) {
2017-03-02 13:34:27 +03:00
if ( $readOnly ) {
// New share can not have more permissions then the old one.
continue ;
}
if ( isset ( $calendars [ $row [ 'id' ]][ $readOnlyPropertyName ]) &&
$calendars [ $row [ 'id' ]][ $readOnlyPropertyName ] === 0 ) {
// Old share is already read-write, no more permissions can be gained
continue ;
}
2017-03-02 13:24:56 +03:00
}
2016-01-26 14:06:02 +03:00
list (, $name ) = URLUtil :: splitPath ( $row [ 'principaluri' ]);
$uri = $row [ 'uri' ] . '_shared_by_' . $name ;
2016-08-18 16:10:18 +03:00
$row [ 'displayname' ] = $row [ 'displayname' ] . ' (' . $this -> getUserDisplayName ( $name ) . ')' ;
2016-01-26 14:06:02 +03:00
$components = [];
if ( $row [ 'components' ]) {
$components = explode ( ',' , $row [ 'components' ]);
}
$calendar = [
'id' => $row [ 'id' ],
'uri' => $uri ,
2016-12-15 18:59:46 +03:00
'principaluri' => $this -> convertPrincipal ( $principalUri , ! $this -> legacyEndpoint ),
2016-01-26 14:06:02 +03:00
'{' . Plugin :: NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ( $row [ 'synctoken' ] ? $row [ 'synctoken' ] : '0' ),
'{http://sabredav.org/ns}sync-token' => $row [ 'synctoken' ] ? $row [ 'synctoken' ] : '0' ,
'{' . Plugin :: NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet ( $components ),
'{' . Plugin :: NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp ( $row [ 'transparent' ] ? 'transparent' : 'opaque' ),
2016-12-15 18:59:46 +03:00
'{' . \OCA\DAV\DAV\Sharing\Plugin :: NS_OWNCLOUD . '}owner-principal' => $this -> convertPrincipal ( $row [ 'principaluri' ], ! $this -> legacyEndpoint ),
2017-03-02 13:34:27 +03:00
$readOnlyPropertyName => $readOnly ,
2016-01-26 14:06:02 +03:00
];
foreach ( $this -> propertyMap as $xmlName => $dbName ) {
$calendar [ $xmlName ] = $row [ $dbName ];
}
2017-04-19 17:18:44 +03:00
$this -> addOwnerPrincipal ( $calendar );
2017-03-02 13:24:56 +03:00
$calendars [ $calendar [ 'id' ]] = $calendar ;
2016-01-26 14:06:02 +03:00
}
$result -> closeCursor ();
2016-01-28 19:16:11 +03:00
return array_values ( $calendars );
2015-10-31 03:28:21 +03:00
}
2016-09-25 14:35:53 +03:00
public function getUsersOwnCalendars ( $principalUri ) {
$principalUri = $this -> convertPrincipal ( $principalUri , true );
$fields = array_values ( $this -> propertyMap );
$fields [] = 'id' ;
$fields [] = 'uri' ;
$fields [] = 'synctoken' ;
$fields [] = 'components' ;
$fields [] = 'principaluri' ;
$fields [] = 'transparent' ;
// Making fields a comma-delimited list
$query = $this -> db -> getQueryBuilder ();
$query -> select ( $fields ) -> from ( 'calendars' )
-> where ( $query -> expr () -> eq ( 'principaluri' , $query -> createNamedParameter ( $principalUri )))
-> orderBy ( 'calendarorder' , 'ASC' );
$stmt = $query -> execute ();
$calendars = [];
while ( $row = $stmt -> fetch ( \PDO :: FETCH_ASSOC )) {
$components = [];
if ( $row [ 'components' ]) {
$components = explode ( ',' , $row [ 'components' ]);
}
$calendar = [
'id' => $row [ 'id' ],
'uri' => $row [ 'uri' ],
2016-12-15 18:59:46 +03:00
'principaluri' => $this -> convertPrincipal ( $row [ 'principaluri' ], ! $this -> legacyEndpoint ),
2016-09-25 14:35:53 +03:00
'{' . Plugin :: NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ( $row [ 'synctoken' ] ? $row [ 'synctoken' ] : '0' ),
'{http://sabredav.org/ns}sync-token' => $row [ 'synctoken' ] ? $row [ 'synctoken' ] : '0' ,
'{' . Plugin :: NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet ( $components ),
'{' . Plugin :: NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp ( $row [ 'transparent' ] ? 'transparent' : 'opaque' ),
];
foreach ( $this -> propertyMap as $xmlName => $dbName ) {
$calendar [ $xmlName ] = $row [ $dbName ];
}
2017-04-19 17:18:44 +03:00
$this -> addOwnerPrincipal ( $calendar );
2016-09-25 14:35:53 +03:00
if ( ! isset ( $calendars [ $calendar [ 'id' ]])) {
$calendars [ $calendar [ 'id' ]] = $calendar ;
}
}
$stmt -> closeCursor ();
return array_values ( $calendars );
}
2016-08-18 16:10:18 +03:00
private function getUserDisplayName ( $uid ) {
if ( ! isset ( $this -> userDisplayNames [ $uid ])) {
$user = $this -> userManager -> get ( $uid );
if ( $user instanceof IUser ) {
$this -> userDisplayNames [ $uid ] = $user -> getDisplayName ();
} else {
$this -> userDisplayNames [ $uid ] = $uid ;
}
}
return $this -> userDisplayNames [ $uid ];
2016-08-01 16:07:22 +03:00
}
/**
* @ return array
*/
2016-07-08 17:13:34 +03:00
public function getPublicCalendars () {
$fields = array_values ( $this -> propertyMap );
$fields [] = 'a.id' ;
$fields [] = 'a.uri' ;
$fields [] = 'a.synctoken' ;
$fields [] = 'a.components' ;
$fields [] = 'a.principaluri' ;
$fields [] = 'a.transparent' ;
$fields [] = 's.access' ;
2016-08-01 17:44:28 +03:00
$fields [] = 's.publicuri' ;
2016-07-08 17:13:34 +03:00
$calendars = [];
$query = $this -> db -> getQueryBuilder ();
$result = $query -> select ( $fields )
-> from ( 'dav_shares' , 's' )
-> join ( 's' , 'calendars' , 'a' , $query -> expr () -> eq ( 's.resourceid' , 'a.id' ))
-> where ( $query -> expr () -> in ( 's.access' , $query -> createNamedParameter ( self :: ACCESS_PUBLIC )))
-> andWhere ( $query -> expr () -> eq ( 's.type' , $query -> createNamedParameter ( 'calendar' )))
-> execute ();
while ( $row = $result -> fetch ()) {
list (, $name ) = URLUtil :: splitPath ( $row [ 'principaluri' ]);
$row [ 'displayname' ] = $row [ 'displayname' ] . " ( $name ) " ;
$components = [];
if ( $row [ 'components' ]) {
$components = explode ( ',' , $row [ 'components' ]);
}
$calendar = [
'id' => $row [ 'id' ],
2016-08-01 17:44:28 +03:00
'uri' => $row [ 'publicuri' ],
2016-12-15 18:59:46 +03:00
'principaluri' => $this -> convertPrincipal ( $row [ 'principaluri' ], ! $this -> legacyEndpoint ),
2016-07-08 17:13:34 +03:00
'{' . Plugin :: NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ( $row [ 'synctoken' ] ? $row [ 'synctoken' ] : '0' ),
'{http://sabredav.org/ns}sync-token' => $row [ 'synctoken' ] ? $row [ 'synctoken' ] : '0' ,
'{' . Plugin :: NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet ( $components ),
'{' . Plugin :: NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp ( $row [ 'transparent' ] ? 'transparent' : 'opaque' ),
2016-12-15 18:59:46 +03:00
'{' . \OCA\DAV\DAV\Sharing\Plugin :: NS_OWNCLOUD . '}owner-principal' => $this -> convertPrincipal ( $row [ 'principaluri' ], $this -> legacyEndpoint ),
2016-07-08 17:13:34 +03:00
'{' . \OCA\DAV\DAV\Sharing\Plugin :: NS_OWNCLOUD . '}read-only' => ( int ) $row [ 'access' ] === Backend :: ACCESS_READ ,
2016-07-08 17:52:20 +03:00
'{' . \OCA\DAV\DAV\Sharing\Plugin :: NS_OWNCLOUD . '}public' => ( int ) $row [ 'access' ] === self :: ACCESS_PUBLIC ,
2016-07-08 17:13:34 +03:00
];
foreach ( $this -> propertyMap as $xmlName => $dbName ) {
$calendar [ $xmlName ] = $row [ $dbName ];
}
2017-04-19 17:18:44 +03:00
$this -> addOwnerPrincipal ( $calendar );
2016-07-08 17:13:34 +03:00
if ( ! isset ( $calendars [ $calendar [ 'id' ]])) {
$calendars [ $calendar [ 'id' ]] = $calendar ;
}
}
$result -> closeCursor ();
return array_values ( $calendars );
2016-08-01 16:07:22 +03:00
}
/**
* @ param string $uri
2016-08-01 17:44:28 +03:00
* @ return array
2016-08-01 16:07:22 +03:00
* @ throws NotFound
*/
public function getPublicCalendar ( $uri ) {
2016-08-01 17:44:28 +03:00
$fields = array_values ( $this -> propertyMap );
$fields [] = 'a.id' ;
$fields [] = 'a.uri' ;
$fields [] = 'a.synctoken' ;
$fields [] = 'a.components' ;
$fields [] = 'a.principaluri' ;
$fields [] = 'a.transparent' ;
$fields [] = 's.access' ;
$fields [] = 's.publicuri' ;
$query = $this -> db -> getQueryBuilder ();
$result = $query -> select ( $fields )
-> from ( 'dav_shares' , 's' )
-> join ( 's' , 'calendars' , 'a' , $query -> expr () -> eq ( 's.resourceid' , 'a.id' ))
-> where ( $query -> expr () -> in ( 's.access' , $query -> createNamedParameter ( self :: ACCESS_PUBLIC )))
-> andWhere ( $query -> expr () -> eq ( 's.type' , $query -> createNamedParameter ( 'calendar' )))
-> andWhere ( $query -> expr () -> eq ( 's.publicuri' , $query -> createNamedParameter ( $uri )))
-> execute ();
$row = $result -> fetch ( \PDO :: FETCH_ASSOC );
$result -> closeCursor ();
if ( $row === false ) {
throw new NotFound ( 'Node with name \'' . $uri . '\' could not be found' );
2016-08-01 16:07:22 +03:00
}
2016-08-01 17:44:28 +03:00
list (, $name ) = URLUtil :: splitPath ( $row [ 'principaluri' ]);
2016-08-11 17:55:57 +03:00
$row [ 'displayname' ] = $row [ 'displayname' ] . ' ' . " ( $name ) " ;
2016-08-01 17:44:28 +03:00
$components = [];
if ( $row [ 'components' ]) {
$components = explode ( ',' , $row [ 'components' ]);
}
$calendar = [
'id' => $row [ 'id' ],
2016-09-03 11:52:05 +03:00
'uri' => $row [ 'publicuri' ],
2016-12-15 18:59:46 +03:00
'principaluri' => $this -> convertPrincipal ( $row [ 'principaluri' ], ! $this -> legacyEndpoint ),
2016-08-01 17:44:28 +03:00
'{' . Plugin :: NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ( $row [ 'synctoken' ] ? $row [ 'synctoken' ] : '0' ),
'{http://sabredav.org/ns}sync-token' => $row [ 'synctoken' ] ? $row [ 'synctoken' ] : '0' ,
'{' . Plugin :: NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet ( $components ),
'{' . Plugin :: NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp ( $row [ 'transparent' ] ? 'transparent' : 'opaque' ),
2016-12-15 18:59:46 +03:00
'{' . \OCA\DAV\DAV\Sharing\Plugin :: NS_OWNCLOUD . '}owner-principal' => $this -> convertPrincipal ( $row [ 'principaluri' ], ! $this -> legacyEndpoint ),
2016-08-01 17:44:28 +03:00
'{' . \OCA\DAV\DAV\Sharing\Plugin :: NS_OWNCLOUD . '}read-only' => ( int ) $row [ 'access' ] === Backend :: ACCESS_READ ,
'{' . \OCA\DAV\DAV\Sharing\Plugin :: NS_OWNCLOUD . '}public' => ( int ) $row [ 'access' ] === self :: ACCESS_PUBLIC ,
];
foreach ( $this -> propertyMap as $xmlName => $dbName ) {
$calendar [ $xmlName ] = $row [ $dbName ];
}
2017-04-19 17:18:44 +03:00
$this -> addOwnerPrincipal ( $calendar );
2016-08-03 12:09:45 +03:00
return $calendar ;
2016-08-01 17:44:28 +03:00
2016-08-18 16:10:18 +03:00
}
2016-02-03 17:43:45 +03:00
/**
* @ param string $principal
* @ param string $uri
* @ return array | null
*/
2016-01-25 19:18:47 +03:00
public function getCalendarByUri ( $principal , $uri ) {
$fields = array_values ( $this -> propertyMap );
$fields [] = 'id' ;
$fields [] = 'uri' ;
$fields [] = 'synctoken' ;
$fields [] = 'components' ;
$fields [] = 'principaluri' ;
$fields [] = 'transparent' ;
// Making fields a comma-delimited list
$query = $this -> db -> getQueryBuilder ();
$query -> select ( $fields ) -> from ( 'calendars' )
-> where ( $query -> expr () -> eq ( 'uri' , $query -> createNamedParameter ( $uri )))
-> andWhere ( $query -> expr () -> eq ( 'principaluri' , $query -> createNamedParameter ( $principal )))
-> setMaxResults ( 1 );
$stmt = $query -> execute ();
$row = $stmt -> fetch ( \PDO :: FETCH_ASSOC );
$stmt -> closeCursor ();
if ( $row === false ) {
return null ;
}
$components = [];
if ( $row [ 'components' ]) {
$components = explode ( ',' , $row [ 'components' ]);
}
$calendar = [
'id' => $row [ 'id' ],
'uri' => $row [ 'uri' ],
2016-12-15 18:59:46 +03:00
'principaluri' => $this -> convertPrincipal ( $row [ 'principaluri' ], ! $this -> legacyEndpoint ),
2016-01-25 19:18:47 +03:00
'{' . Plugin :: NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ( $row [ 'synctoken' ] ? $row [ 'synctoken' ] : '0' ),
'{http://sabredav.org/ns}sync-token' => $row [ 'synctoken' ] ? $row [ 'synctoken' ] : '0' ,
'{' . Plugin :: NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet ( $components ),
'{' . Plugin :: NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp ( $row [ 'transparent' ] ? 'transparent' : 'opaque' ),
];
foreach ( $this -> propertyMap as $xmlName => $dbName ) {
$calendar [ $xmlName ] = $row [ $dbName ];
}
2017-04-19 17:18:44 +03:00
$this -> addOwnerPrincipal ( $calendar );
2016-01-25 19:18:47 +03:00
return $calendar ;
}
public function getCalendarById ( $calendarId ) {
$fields = array_values ( $this -> propertyMap );
$fields [] = 'id' ;
$fields [] = 'uri' ;
$fields [] = 'synctoken' ;
$fields [] = 'components' ;
$fields [] = 'principaluri' ;
$fields [] = 'transparent' ;
// Making fields a comma-delimited list
$query = $this -> db -> getQueryBuilder ();
$query -> select ( $fields ) -> from ( 'calendars' )
-> where ( $query -> expr () -> eq ( 'id' , $query -> createNamedParameter ( $calendarId )))
-> setMaxResults ( 1 );
$stmt = $query -> execute ();
$row = $stmt -> fetch ( \PDO :: FETCH_ASSOC );
$stmt -> closeCursor ();
if ( $row === false ) {
return null ;
}
$components = [];
if ( $row [ 'components' ]) {
$components = explode ( ',' , $row [ 'components' ]);
}
$calendar = [
'id' => $row [ 'id' ],
'uri' => $row [ 'uri' ],
2016-12-15 18:59:46 +03:00
'principaluri' => $this -> convertPrincipal ( $row [ 'principaluri' ], ! $this -> legacyEndpoint ),
2016-01-25 19:18:47 +03:00
'{' . Plugin :: NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ( $row [ 'synctoken' ] ? $row [ 'synctoken' ] : '0' ),
'{http://sabredav.org/ns}sync-token' => $row [ 'synctoken' ] ? $row [ 'synctoken' ] : '0' ,
'{' . Plugin :: NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet ( $components ),
'{' . Plugin :: NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp ( $row [ 'transparent' ] ? 'transparent' : 'opaque' ),
];
foreach ( $this -> propertyMap as $xmlName => $dbName ) {
$calendar [ $xmlName ] = $row [ $dbName ];
}
2017-04-19 17:18:44 +03:00
$this -> addOwnerPrincipal ( $calendar );
2016-01-25 19:18:47 +03:00
return $calendar ;
}
2015-10-31 03:28:21 +03:00
/**
* Creates a new calendar for a principal .
*
* If the creation was a success , an id must be returned that can be used to reference
* this calendar in other methods , such as updateCalendar .
*
* @ param string $principalUri
* @ param string $calendarUri
* @ param array $properties
2016-01-25 19:18:47 +03:00
* @ return int
2015-10-31 03:28:21 +03:00
*/
function createCalendar ( $principalUri , $calendarUri , array $properties ) {
$values = [
2016-12-15 02:40:12 +03:00
'principaluri' => $this -> convertPrincipal ( $principalUri , true ),
2015-10-31 03:28:21 +03:00
'uri' => $calendarUri ,
'synctoken' => 1 ,
'transparent' => 0 ,
2015-11-16 23:01:27 +03:00
'components' => 'VEVENT,VTODO' ,
'displayname' => $calendarUri
2015-10-31 03:28:21 +03:00
];
// Default value
$sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set' ;
if ( isset ( $properties [ $sccs ])) {
if ( ! ( $properties [ $sccs ] instanceof SupportedCalendarComponentSet )) {
throw new DAV\Exception ( 'The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Property\SupportedCalendarComponentSet' );
}
$values [ 'components' ] = implode ( ',' , $properties [ $sccs ] -> getValue ());
}
$transp = '{' . Plugin :: NS_CALDAV . '}schedule-calendar-transp' ;
if ( isset ( $properties [ $transp ])) {
2017-03-30 18:58:12 +03:00
$values [ 'transparent' ] = ( int ) ( $properties [ $transp ] -> getValue () === 'transparent' );
2015-10-31 03:28:21 +03:00
}
foreach ( $this -> propertyMap as $xmlName => $dbName ) {
if ( isset ( $properties [ $xmlName ])) {
$values [ $dbName ] = $properties [ $xmlName ];
}
}
$query = $this -> db -> getQueryBuilder ();
2015-11-16 23:01:27 +03:00
$query -> insert ( 'calendars' );
foreach ( $values as $column => $value ) {
$query -> setValue ( $column , $query -> createNamedParameter ( $value ));
}
$query -> execute ();
2016-09-26 14:50:39 +03:00
$calendarId = $query -> getLastInsertId ();
2016-10-13 16:34:26 +03:00
$this -> dispatcher -> dispatch ( '\OCA\DAV\CalDAV\CalDavBackend::createCalendar' , new GenericEvent (
'\OCA\DAV\CalDAV\CalDavBackend::createCalendar' ,
[
'calendarId' => $calendarId ,
'calendarData' => $this -> getCalendarById ( $calendarId ),
]));
2016-09-26 14:50:39 +03:00
return $calendarId ;
2015-10-31 03:28:21 +03:00
}
/**
* Updates properties for a calendar .
*
* The list of mutations is stored in a Sabre\DAV\PropPatch object .
* To do the actual updates , you must tell this object which properties
* you ' re going to process with the handle () method .
*
* Calling the handle method is like telling the PropPatch object " I
* promise I can handle updating this property " .
*
* Read the PropPatch documentation for more info and examples .
*
2016-04-19 12:33:37 +03:00
* @ param PropPatch $propPatch
2015-10-31 03:28:21 +03:00
* @ return void
*/
2016-04-19 12:33:37 +03:00
function updateCalendar ( $calendarId , PropPatch $propPatch ) {
2015-10-31 03:28:21 +03:00
$supportedProperties = array_keys ( $this -> propertyMap );
$supportedProperties [] = '{' . Plugin :: NS_CALDAV . '}schedule-calendar-transp' ;
$propPatch -> handle ( $supportedProperties , function ( $mutations ) use ( $calendarId ) {
$newValues = [];
foreach ( $mutations as $propertyName => $propertyValue ) {
switch ( $propertyName ) {
case '{' . Plugin :: NS_CALDAV . '}schedule-calendar-transp' :
$fieldName = 'transparent' ;
2017-03-30 18:58:12 +03:00
$newValues [ $fieldName ] = ( int ) ( $propertyValue -> getValue () === 'transparent' );
2015-10-31 03:28:21 +03:00
break ;
default :
$fieldName = $this -> propertyMap [ $propertyName ];
$newValues [ $fieldName ] = $propertyValue ;
break ;
}
}
$query = $this -> db -> getQueryBuilder ();
$query -> update ( 'calendars' );
foreach ( $newValues as $fieldName => $value ) {
$query -> set ( $fieldName , $query -> createNamedParameter ( $value ));
}
$query -> where ( $query -> expr () -> eq ( 'id' , $query -> createNamedParameter ( $calendarId )));
$query -> execute ();
$this -> addChange ( $calendarId , " " , 2 );
2016-10-13 16:34:26 +03:00
$this -> dispatcher -> dispatch ( '\OCA\DAV\CalDAV\CalDavBackend::updateCalendar' , new GenericEvent (
'\OCA\DAV\CalDAV\CalDavBackend::updateCalendar' ,
[
'calendarId' => $calendarId ,
'calendarData' => $this -> getCalendarById ( $calendarId ),
'shares' => $this -> getShares ( $calendarId ),
'propertyMutations' => $mutations ,
]));
2016-09-26 14:50:39 +03:00
2015-10-31 03:28:21 +03:00
return true ;
});
}
/**
* Delete a calendar and all it ' s objects
*
* @ param mixed $calendarId
* @ return void
*/
function deleteCalendar ( $calendarId ) {
2016-10-13 16:34:26 +03:00
$this -> dispatcher -> dispatch ( '\OCA\DAV\CalDAV\CalDavBackend::deleteCalendar' , new GenericEvent (
'\OCA\DAV\CalDAV\CalDavBackend::deleteCalendar' ,
[
'calendarId' => $calendarId ,
'calendarData' => $this -> getCalendarById ( $calendarId ),
'shares' => $this -> getShares ( $calendarId ),
]));
2016-09-26 14:50:39 +03:00
2015-10-31 03:28:21 +03:00
$stmt = $this -> db -> prepare ( 'DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ?' );
$stmt -> execute ([ $calendarId ]);
$stmt = $this -> db -> prepare ( 'DELETE FROM `*PREFIX*calendars` WHERE `id` = ?' );
$stmt -> execute ([ $calendarId ]);
$stmt = $this -> db -> prepare ( 'DELETE FROM `*PREFIX*calendarchanges` WHERE `calendarid` = ?' );
$stmt -> execute ([ $calendarId ]);
2016-02-03 22:18:56 +03:00
$this -> sharingBackend -> deleteAllShares ( $calendarId );
2017-03-25 13:56:40 +03:00
$query = $this -> db -> getQueryBuilder ();
$query -> delete ( $this -> dbObjectPropertiesTable )
-> where ( $query -> expr () -> eq ( 'calendarid' , $query -> createNamedParameter ( $calendarId )))
-> execute ();
2015-10-31 03:28:21 +03:00
}
2016-09-23 15:30:24 +03:00
/**
* Delete all of an user ' s shares
*
* @ param string $principaluri
* @ return void
*/
2016-09-28 16:32:03 +03:00
function deleteAllSharesByUser ( $principaluri ) {
2016-09-23 15:30:24 +03:00
$this -> sharingBackend -> deleteAllSharesByUser ( $principaluri );
}
2015-10-31 03:28:21 +03:00
/**
* Returns all calendar objects within a calendar .
*
* Every item contains an array with the following keys :
* * calendardata - The iCalendar - compatible calendar data
* * uri - a unique key which will be used to construct the uri . This can
* be any arbitrary string , but making sure it ends with '.ics' is a
* good idea . This is only the basename , or filename , not the full
* path .
* * lastmodified - a timestamp of the last modification time
* * etag - An arbitrary string , surrounded by double - quotes . ( e . g .:
* '"abcdef"' )
* * size - The size of the calendar objects , in bytes .
* * component - optional , a string containing the type of object , such
* as 'vevent' or 'vtodo' . If specified , this will be used to populate
* the Content - Type header .
*
* Note that the etag is optional , but it ' s highly encouraged to return for
* speed reasons .
*
* The calendardata is also optional . If it ' s not returned
* 'getCalendarObject' will be called later , which * is * expected to return
* calendardata .
*
* If neither etag or size are specified , the calendardata will be
* used / fetched to determine these numbers . If both are specified the
* amount of times this is needed is reduced by a great degree .
*
* @ param mixed $calendarId
* @ return array
*/
function getCalendarObjects ( $calendarId ) {
$query = $this -> db -> getQueryBuilder ();
2016-05-31 18:16:28 +03:00
$query -> select ([ 'id' , 'uri' , 'lastmodified' , 'etag' , 'calendarid' , 'size' , 'componenttype' , 'classification' ])
2015-10-31 03:28:21 +03:00
-> from ( 'calendarobjects' )
-> where ( $query -> expr () -> eq ( 'calendarid' , $query -> createNamedParameter ( $calendarId )));
$stmt = $query -> execute ();
$result = [];
foreach ( $stmt -> fetchAll ( \PDO :: FETCH_ASSOC ) as $row ) {
$result [] = [
'id' => $row [ 'id' ],
'uri' => $row [ 'uri' ],
'lastmodified' => $row [ 'lastmodified' ],
'etag' => '"' . $row [ 'etag' ] . '"' ,
'calendarid' => $row [ 'calendarid' ],
'size' => ( int ) $row [ 'size' ],
'component' => strtolower ( $row [ 'componenttype' ]),
2016-05-31 19:10:31 +03:00
'classification' => ( int ) $row [ 'classification' ]
2015-10-31 03:28:21 +03:00
];
}
return $result ;
}
/**
* Returns information from a single calendar object , based on it ' s object
* uri .
*
* The object uri is only the basename , or filename and not a full path .
*
* The returned array must have the same keys as getCalendarObjects . The
* 'calendardata' object is required here though , while it ' s not required
* for getCalendarObjects .
*
* This method must return null if the object did not exist .
*
* @ param mixed $calendarId
* @ param string $objectUri
* @ return array | null
*/
function getCalendarObject ( $calendarId , $objectUri ) {
$query = $this -> db -> getQueryBuilder ();
2016-05-31 18:16:28 +03:00
$query -> select ([ 'id' , 'uri' , 'lastmodified' , 'etag' , 'calendarid' , 'size' , 'calendardata' , 'componenttype' , 'classification' ])
2015-10-31 03:28:21 +03:00
-> from ( 'calendarobjects' )
-> where ( $query -> expr () -> eq ( 'calendarid' , $query -> createNamedParameter ( $calendarId )))
-> andWhere ( $query -> expr () -> eq ( 'uri' , $query -> createNamedParameter ( $objectUri )));
$stmt = $query -> execute ();
$row = $stmt -> fetch ( \PDO :: FETCH_ASSOC );
if ( ! $row ) return null ;
return [
'id' => $row [ 'id' ],
'uri' => $row [ 'uri' ],
'lastmodified' => $row [ 'lastmodified' ],
'etag' => '"' . $row [ 'etag' ] . '"' ,
'calendarid' => $row [ 'calendarid' ],
'size' => ( int ) $row [ 'size' ],
'calendardata' => $this -> readBlob ( $row [ 'calendardata' ]),
'component' => strtolower ( $row [ 'componenttype' ]),
2016-05-31 19:10:31 +03:00
'classification' => ( int ) $row [ 'classification' ]
2015-10-31 03:28:21 +03:00
];
}
/**
* Returns a list of calendar objects .
*
* This method should work identical to getCalendarObject , but instead
* return all the calendar objects in the list as an array .
*
* If the backend supports this , it may allow for some speed - ups .
*
* @ param mixed $calendarId
2015-11-20 18:42:34 +03:00
* @ param string [] $uris
2015-10-31 03:28:21 +03:00
* @ return array
*/
function getMultipleCalendarObjects ( $calendarId , array $uris ) {
2016-09-14 17:29:33 +03:00
if ( empty ( $uris )) {
return [];
}
$chunks = array_chunk ( $uris , 100 );
2016-09-15 10:47:39 +03:00
$objects = [];
2016-09-14 17:29:33 +03:00
2015-10-31 03:28:21 +03:00
$query = $this -> db -> getQueryBuilder ();
2016-05-31 18:16:28 +03:00
$query -> select ([ 'id' , 'uri' , 'lastmodified' , 'etag' , 'calendarid' , 'size' , 'calendardata' , 'componenttype' , 'classification' ])
2016-09-14 17:29:33 +03:00
-> from ( 'calendarobjects' )
-> where ( $query -> expr () -> eq ( 'calendarid' , $query -> createNamedParameter ( $calendarId )))
-> andWhere ( $query -> expr () -> in ( 'uri' , $query -> createParameter ( 'uri' )));
2015-10-31 03:28:21 +03:00
2016-09-14 17:29:33 +03:00
foreach ( $chunks as $uris ) {
$query -> setParameter ( 'uri' , $uris , IQueryBuilder :: PARAM_STR_ARRAY );
2016-09-15 10:47:39 +03:00
$result = $query -> execute ();
2015-10-31 03:28:21 +03:00
2016-09-15 10:47:39 +03:00
while ( $row = $result -> fetch ()) {
$objects [] = [
2015-10-31 03:28:21 +03:00
'id' => $row [ 'id' ],
'uri' => $row [ 'uri' ],
'lastmodified' => $row [ 'lastmodified' ],
'etag' => '"' . $row [ 'etag' ] . '"' ,
'calendarid' => $row [ 'calendarid' ],
'size' => ( int ) $row [ 'size' ],
'calendardata' => $this -> readBlob ( $row [ 'calendardata' ]),
'component' => strtolower ( $row [ 'componenttype' ]),
2016-05-31 19:10:31 +03:00
'classification' => ( int ) $row [ 'classification' ]
2016-09-14 17:29:33 +03:00
];
}
2016-09-15 10:47:39 +03:00
$result -> closeCursor ();
2015-10-31 03:28:21 +03:00
}
2016-09-15 10:47:39 +03:00
return $objects ;
2015-10-31 03:28:21 +03:00
}
/**
* Creates a new calendar object .
*
* The object uri is only the basename , or filename and not a full path .
*
* It is possible return an etag from this function , which will be used in
* the response to this PUT request . Note that the ETag must be surrounded
* by double - quotes .
*
* However , you should only really return this ETag if you don ' t mangle the
* calendar - data . If the result of a subsequent GET to this object is not
* the exact same as this request body , you should omit the ETag .
*
* @ param mixed $calendarId
* @ param string $objectUri
* @ param string $calendarData
2015-11-20 18:42:34 +03:00
* @ return string
2015-10-31 03:28:21 +03:00
*/
function createCalendarObject ( $calendarId , $objectUri , $calendarData ) {
$extraData = $this -> getDenormalizedData ( $calendarData );
$query = $this -> db -> getQueryBuilder ();
$query -> insert ( 'calendarobjects' )
-> values ([
'calendarid' => $query -> createNamedParameter ( $calendarId ),
'uri' => $query -> createNamedParameter ( $objectUri ),
2016-02-29 11:44:40 +03:00
'calendardata' => $query -> createNamedParameter ( $calendarData , IQueryBuilder :: PARAM_LOB ),
2015-10-31 03:28:21 +03:00
'lastmodified' => $query -> createNamedParameter ( time ()),
'etag' => $query -> createNamedParameter ( $extraData [ 'etag' ]),
'size' => $query -> createNamedParameter ( $extraData [ 'size' ]),
'componenttype' => $query -> createNamedParameter ( $extraData [ 'componentType' ]),
'firstoccurence' => $query -> createNamedParameter ( $extraData [ 'firstOccurence' ]),
'lastoccurence' => $query -> createNamedParameter ( $extraData [ 'lastOccurence' ]),
2016-04-19 12:33:37 +03:00
'classification' => $query -> createNamedParameter ( $extraData [ 'classification' ]),
2015-10-31 03:28:21 +03:00
'uid' => $query -> createNamedParameter ( $extraData [ 'uid' ]),
])
-> execute ();
2017-03-25 13:56:40 +03:00
$this -> updateProperties ( $calendarId , $objectUri , $calendarData );
2016-10-13 16:34:26 +03:00
$this -> dispatcher -> dispatch ( '\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject' , new GenericEvent (
'\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject' ,
[
'calendarId' => $calendarId ,
'calendarData' => $this -> getCalendarById ( $calendarId ),
'shares' => $this -> getShares ( $calendarId ),
'objectData' => $this -> getCalendarObject ( $calendarId , $objectUri ),
]
));
2015-10-31 03:28:21 +03:00
$this -> addChange ( $calendarId , $objectUri , 1 );
return '"' . $extraData [ 'etag' ] . '"' ;
}
/**
* Updates an existing calendarobject , based on it ' s uri .
*
* The object uri is only the basename , or filename and not a full path .
*
* It is possible return an etag from this function , which will be used in
* the response to this PUT request . Note that the ETag must be surrounded
* by double - quotes .
*
* However , you should only really return this ETag if you don ' t mangle the
* calendar - data . If the result of a subsequent GET to this object is not
* the exact same as this request body , you should omit the ETag .
*
* @ param mixed $calendarId
* @ param string $objectUri
* @ param string $calendarData
2015-11-20 18:42:34 +03:00
* @ return string
2015-10-31 03:28:21 +03:00
*/
function updateCalendarObject ( $calendarId , $objectUri , $calendarData ) {
$extraData = $this -> getDenormalizedData ( $calendarData );
$query = $this -> db -> getQueryBuilder ();
$query -> update ( 'calendarobjects' )
2016-02-29 11:44:40 +03:00
-> set ( 'calendardata' , $query -> createNamedParameter ( $calendarData , IQueryBuilder :: PARAM_LOB ))
2015-10-31 03:28:21 +03:00
-> set ( 'lastmodified' , $query -> createNamedParameter ( time ()))
-> set ( 'etag' , $query -> createNamedParameter ( $extraData [ 'etag' ]))
-> set ( 'size' , $query -> createNamedParameter ( $extraData [ 'size' ]))
-> set ( 'componenttype' , $query -> createNamedParameter ( $extraData [ 'componentType' ]))
-> set ( 'firstoccurence' , $query -> createNamedParameter ( $extraData [ 'firstOccurence' ]))
-> set ( 'lastoccurence' , $query -> createNamedParameter ( $extraData [ 'lastOccurence' ]))
2016-04-19 12:33:37 +03:00
-> set ( 'classification' , $query -> createNamedParameter ( $extraData [ 'classification' ]))
2015-10-31 03:28:21 +03:00
-> set ( 'uid' , $query -> createNamedParameter ( $extraData [ 'uid' ]))
-> where ( $query -> expr () -> eq ( 'calendarid' , $query -> createNamedParameter ( $calendarId )))
-> andWhere ( $query -> expr () -> eq ( 'uri' , $query -> createNamedParameter ( $objectUri )))
-> execute ();
2017-03-25 13:56:40 +03:00
$this -> updateProperties ( $calendarId , $objectUri , $calendarData );
2016-10-13 16:34:26 +03:00
$data = $this -> getCalendarObject ( $calendarId , $objectUri );
if ( is_array ( $data )) {
$this -> dispatcher -> dispatch ( '\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject' , new GenericEvent (
'\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject' ,
[
'calendarId' => $calendarId ,
'calendarData' => $this -> getCalendarById ( $calendarId ),
'shares' => $this -> getShares ( $calendarId ),
'objectData' => $data ,
]
));
}
2015-10-31 03:28:21 +03:00
$this -> addChange ( $calendarId , $objectUri , 2 );
return '"' . $extraData [ 'etag' ] . '"' ;
}
2016-04-19 12:33:37 +03:00
/**
* @ param int $calendarObjectId
* @ param int $classification
*/
public function setClassification ( $calendarObjectId , $classification ) {
2016-05-31 18:16:28 +03:00
if ( ! in_array ( $classification , [
self :: CLASSIFICATION_PUBLIC , self :: CLASSIFICATION_PRIVATE , self :: CLASSIFICATION_CONFIDENTIAL
])) {
throw new \InvalidArgumentException ();
}
2016-04-19 12:33:37 +03:00
$query = $this -> db -> getQueryBuilder ();
$query -> update ( 'calendarobjects' )
-> set ( 'classification' , $query -> createNamedParameter ( $classification ))
-> where ( $query -> expr () -> eq ( 'id' , $query -> createNamedParameter ( $calendarObjectId )))
-> execute ();
}
2015-10-31 03:28:21 +03:00
/**
* Deletes an existing calendar object .
*
* The object uri is only the basename , or filename and not a full path .
*
* @ param mixed $calendarId
* @ param string $objectUri
* @ return void
*/
function deleteCalendarObject ( $calendarId , $objectUri ) {
2016-10-13 16:34:26 +03:00
$data = $this -> getCalendarObject ( $calendarId , $objectUri );
if ( is_array ( $data )) {
$this -> dispatcher -> dispatch ( '\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject' , new GenericEvent (
'\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject' ,
[
'calendarId' => $calendarId ,
'calendarData' => $this -> getCalendarById ( $calendarId ),
'shares' => $this -> getShares ( $calendarId ),
'objectData' => $data ,
]
));
}
2016-10-13 13:16:43 +03:00
2015-10-31 03:28:21 +03:00
$stmt = $this -> db -> prepare ( 'DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `uri` = ?' );
$stmt -> execute ([ $calendarId , $objectUri ]);
2017-03-25 13:56:40 +03:00
$this -> purgeProperties ( $calendarId , $data [ 'id' ]);
2015-10-31 03:28:21 +03:00
$this -> addChange ( $calendarId , $objectUri , 3 );
}
/**
* Performs a calendar - query on the contents of this calendar .
*
* The calendar - query is defined in RFC4791 : CalDAV . Using the
* calendar - query it is possible for a client to request a specific set of
* object , based on contents of iCalendar properties , date - ranges and
* iCalendar component types ( VTODO , VEVENT ) .
*
* This method should just return a list of ( relative ) urls that match this
* query .
*
* The list of filters are specified as an array . The exact array is
* documented by Sabre\CalDAV\CalendarQueryParser .
*
* Note that it is extremely likely that getCalendarObject for every path
* returned from this method will be called almost immediately after . You
* may want to anticipate this to speed up these requests .
*
* This method provides a default implementation , which parses * all * the
* iCalendar objects in the specified calendar .
*
* This default may well be good enough for personal use , and calendars
* that aren ' t very large . But if you anticipate high usage , big calendars
2016-01-29 20:25:27 +03:00
* or high loads , you are strongly advised to optimize certain paths .
2015-10-31 03:28:21 +03:00
*
* The best way to do so is override this method and to optimize
* specifically for 'common filters' .
*
* Requests that are extremely common are :
* * requests for just VEVENTS
* * requests for just VTODO
* * requests with a time - range - filter on either VEVENT or VTODO .
*
* .. and combinations of these requests . It may not be worth it to try to
* handle every possible situation and just rely on the ( relatively
* easy to use ) CalendarQueryValidator to handle the rest .
*
* Note that especially time - range - filters may be difficult to parse . A
* time - range filter specified on a VEVENT must for instance also handle
* recurrence rules correctly .
* A good example of how to interprete all these filters can also simply
* be found in Sabre\CalDAV\CalendarQueryFilter . This class is as correct
* as possible , so it gives you a good idea on what type of stuff you need
* to think of .
*
* @ param mixed $calendarId
* @ param array $filters
* @ return array
*/
function calendarQuery ( $calendarId , array $filters ) {
$componentType = null ;
$requirePostFilter = true ;
$timeRange = null ;
// if no filters were specified, we don't need to filter after a query
if ( ! $filters [ 'prop-filters' ] && ! $filters [ 'comp-filters' ]) {
$requirePostFilter = false ;
}
// Figuring out if there's a component filter
if ( count ( $filters [ 'comp-filters' ]) > 0 && ! $filters [ 'comp-filters' ][ 0 ][ 'is-not-defined' ]) {
$componentType = $filters [ 'comp-filters' ][ 0 ][ 'name' ];
// Checking if we need post-filters
if ( ! $filters [ 'prop-filters' ] && ! $filters [ 'comp-filters' ][ 0 ][ 'comp-filters' ] && ! $filters [ 'comp-filters' ][ 0 ][ 'time-range' ] && ! $filters [ 'comp-filters' ][ 0 ][ 'prop-filters' ]) {
$requirePostFilter = false ;
}
// There was a time-range filter
if ( $componentType == 'VEVENT' && isset ( $filters [ 'comp-filters' ][ 0 ][ 'time-range' ])) {
$timeRange = $filters [ 'comp-filters' ][ 0 ][ 'time-range' ];
// If start time OR the end time is not specified, we can do a
// 100% accurate mysql query.
if ( ! $filters [ 'prop-filters' ] && ! $filters [ 'comp-filters' ][ 0 ][ 'comp-filters' ] && ! $filters [ 'comp-filters' ][ 0 ][ 'prop-filters' ] && ( ! $timeRange [ 'start' ] || ! $timeRange [ 'end' ])) {
$requirePostFilter = false ;
}
}
}
$columns = [ 'uri' ];
if ( $requirePostFilter ) {
$columns = [ 'uri' , 'calendardata' ];
}
$query = $this -> db -> getQueryBuilder ();
$query -> select ( $columns )
-> from ( 'calendarobjects' )
-> where ( $query -> expr () -> eq ( 'calendarid' , $query -> createNamedParameter ( $calendarId )));
if ( $componentType ) {
$query -> andWhere ( $query -> expr () -> eq ( 'componenttype' , $query -> createNamedParameter ( $componentType )));
}
if ( $timeRange && $timeRange [ 'start' ]) {
$query -> andWhere ( $query -> expr () -> gt ( 'lastoccurence' , $query -> createNamedParameter ( $timeRange [ 'start' ] -> getTimeStamp ())));
}
if ( $timeRange && $timeRange [ 'end' ]) {
$query -> andWhere ( $query -> expr () -> lt ( 'firstoccurence' , $query -> createNamedParameter ( $timeRange [ 'end' ] -> getTimeStamp ())));
}
$stmt = $query -> execute ();
$result = [];
while ( $row = $stmt -> fetch ( \PDO :: FETCH_ASSOC )) {
if ( $requirePostFilter ) {
if ( ! $this -> validateFilterForObject ( $row , $filters )) {
continue ;
}
}
$result [] = $row [ 'uri' ];
}
return $result ;
}
2017-03-25 13:56:40 +03:00
/**
* 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 );
}
2017-04-25 20:26:47 +03:00
if ( ! isset ( $filters [ 'props' ])) {
$filters [ 'props' ] = [];
}
if ( ! isset ( $filters [ 'params' ])) {
$filters [ 'params' ] = [];
}
$propParamExpressions = [];
2017-03-25 13:56:40 +03:00
foreach ( $filters [ 'props' ] as $prop ) {
2017-04-25 20:26:47 +03:00
$propParamExpressions [] = $query -> expr () -> andX (
2017-03-25 13:56:40 +03:00
$query -> expr () -> eq ( 'i.name' , $query -> createNamedParameter ( $prop )),
$query -> expr () -> isNull ( 'i.parameter' )
);
}
foreach ( $filters [ 'params' ] as $param ) {
2017-04-25 20:26:47 +03:00
$propParamExpressions [] = $query -> expr () -> andX (
2017-03-25 13:56:40 +03:00
$query -> expr () -> eq ( 'i.name' , $query -> createNamedParameter ( $param [ 'property' ])),
$query -> expr () -> eq ( 'i.parameter' , $query -> createNamedParameter ( $param [ 'parameter' ]))
);
}
2017-04-25 20:26:47 +03:00
if ( count ( $propParamExpressions ) === 1 ) {
$propParamExpr = $propParamExpressions [ 0 ];
2017-03-25 13:56:40 +03:00
} else {
2017-04-25 20:26:47 +03:00
$propParamExpr = call_user_func_array ([ $query -> expr (), 'orX' ], $propParamExpressions );
2017-03-25 13:56:40 +03:00
}
$query -> select ([ 'c.calendarid' , 'c.uri' ])
2017-04-26 11:06:10 +03:00
-> from ( $this -> dbObjectPropertiesTable , 'i' )
2017-03-25 13:56:40 +03:00
-> join ( 'i' , 'calendarobjects' , 'c' , $query -> expr () -> eq ( 'i.objectid' , 'c.id' ))
-> where ( $calExpr )
-> andWhere ( $compExpr )
2017-04-25 20:26:47 +03:00
-> andWhere ( $propParamExpr )
-> andWhere ( $query -> expr () -> iLike ( 'i.value' ,
$query -> createNamedParameter ( '%' . $this -> db -> escapeLikeParameter ( $filters [ 'search-term' ]) . '%' )));
if ( $offset ) {
$query -> setFirstResult ( $offset );
}
if ( $limit ) {
$query -> setMaxResults ( $limit );
}
2017-03-25 13:56:40 +03:00
$stmt = $query -> execute ();
$result = [];
while ( $row = $stmt -> fetch ( \PDO :: FETCH_ASSOC )) {
2017-04-25 20:26:47 +03:00
$path = $uriMapper [ $row [ 'calendarid' ]] . '/' . $row [ 'uri' ];
if ( ! in_array ( $path , $result )) {
$result [] = $path ;
}
2017-03-25 13:56:40 +03:00
}
return $result ;
}
2015-10-31 03:28:21 +03:00
/**
* Searches through all of a users calendars and calendar objects to find
* an object with a specific UID .
*
* This method should return the path to this object , relative to the
* calendar home , so this path usually only contains two parts :
*
* calendarpath / objectpath . ics
*
* If the uid is not found , return null .
*
* This method should only consider * objects that the principal owns , so
* any calendars owned by other principals that also appear in this
* collection should be ignored .
*
* @ param string $principalUri
* @ param string $uid
* @ return string | null
*/
function getCalendarObjectByUID ( $principalUri , $uid ) {
$query = $this -> db -> getQueryBuilder ();
2016-03-10 12:24:08 +03:00
$query -> selectAlias ( 'c.uri' , 'calendaruri' ) -> selectAlias ( 'co.uri' , 'objecturi' )
2015-10-31 03:28:21 +03:00
-> from ( 'calendarobjects' , 'co' )
2016-03-10 12:24:08 +03:00
-> leftJoin ( 'co' , 'calendars' , 'c' , $query -> expr () -> eq ( 'co.calendarid' , 'c.id' ))
2015-10-31 03:28:21 +03:00
-> where ( $query -> expr () -> eq ( 'c.principaluri' , $query -> createNamedParameter ( $principalUri )))
-> andWhere ( $query -> expr () -> eq ( 'co.uid' , $query -> createNamedParameter ( $uid )));
$stmt = $query -> execute ();
if ( $row = $stmt -> fetch ( \PDO :: FETCH_ASSOC )) {
return $row [ 'calendaruri' ] . '/' . $row [ 'objecturi' ];
}
return null ;
}
/**
* The getChanges method returns all the changes that have happened , since
* the specified syncToken in the specified calendar .
*
* This function should return an array , such as the following :
*
* [
* 'syncToken' => 'The current synctoken' ,
* 'added' => [
* 'new.txt' ,
* ],
* 'modified' => [
* 'modified.txt' ,
* ],
* 'deleted' => [
* 'foo.php.bak' ,
* 'old.txt'
* ]
* );
*
* The returned syncToken property should reflect the * current * syncToken
* of the calendar , as reported in the { http :// sabredav . org / ns } sync - token
* property This is * needed here too , to ensure the operation is atomic .
*
* If the $syncToken argument is specified as null , this is an initial
* sync , and all members should be reported .
*
* The modified property is an array of nodenames that have changed since
* the last token .
*
* The deleted property is an array with nodenames , that have been deleted
* from collection .
*
* The $syncLevel argument is basically the 'depth' of the report . If it ' s
* 1 , you only have to report changes that happened only directly in
* immediate descendants . If it ' s 2 , it should also include changes from
* the nodes below the child collections . ( grandchildren )
*
* The $limit argument allows a client to specify how many results should
* be returned at most . If the limit is not specified , it should be treated
* as infinite .
*
* If the limit ( infinite or not ) is higher than you ' re willing to return ,
* you should throw a Sabre\DAV\Exception\TooMuchMatches () exception .
*
* If the syncToken is expired ( due to data cleanup ) or unknown , you must
* return null .
*
* The limit is 'suggestive' . You are free to ignore it .
*
* @ param string $calendarId
* @ param string $syncToken
* @ param int $syncLevel
* @ param int $limit
* @ return array
*/
function getChangesForCalendar ( $calendarId , $syncToken , $syncLevel , $limit = null ) {
// Current synctoken
$stmt = $this -> db -> prepare ( 'SELECT `synctoken` FROM `*PREFIX*calendars` WHERE `id` = ?' );
$stmt -> execute ([ $calendarId ]);
$currentToken = $stmt -> fetchColumn ( 0 );
if ( is_null ( $currentToken )) {
return null ;
}
$result = [
'syncToken' => $currentToken ,
'added' => [],
'modified' => [],
'deleted' => [],
];
if ( $syncToken ) {
$query = " SELECT `uri`, `operation` FROM `*PREFIX*calendarchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `calendarid` = ? ORDER BY `synctoken` " ;
if ( $limit > 0 ) {
$query .= " `LIMIT` " . ( int ) $limit ;
}
// Fetching all changes
$stmt = $this -> db -> prepare ( $query );
$stmt -> execute ([ $syncToken , $currentToken , $calendarId ]);
$changes = [];
// This loop ensures that any duplicates are overwritten, only the
// last change on a node is relevant.
while ( $row = $stmt -> fetch ( \PDO :: FETCH_ASSOC )) {
$changes [ $row [ 'uri' ]] = $row [ 'operation' ];
}
foreach ( $changes as $uri => $operation ) {
switch ( $operation ) {
case 1 :
$result [ 'added' ][] = $uri ;
break ;
case 2 :
$result [ 'modified' ][] = $uri ;
break ;
case 3 :
$result [ 'deleted' ][] = $uri ;
break ;
}
}
} else {
// No synctoken supplied, this is the initial sync.
$query = " SELECT `uri` FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? " ;
$stmt = $this -> db -> prepare ( $query );
$stmt -> execute ([ $calendarId ]);
$result [ 'added' ] = $stmt -> fetchAll ( \PDO :: FETCH_COLUMN );
}
return $result ;
}
/**
* Returns a list of subscriptions for a principal .
*
* Every subscription is an array with the following keys :
* * id , a unique id that will be used by other functions to modify the
* subscription . This can be the same as the uri or a database key .
* * uri . This is just the 'base uri' or 'filename' of the subscription .
* * principaluri . The owner of the subscription . Almost always the same as
* principalUri passed to this method .
*
* Furthermore , all the subscription info must be returned too :
*
* 1. { DAV : } displayname
* 2. { http :// apple . com / ns / ical / } refreshrate
* 3. { http :// calendarserver . org / ns / } subscribed - strip - todos ( omit if todos
* should not be stripped ) .
* 4. { http :// calendarserver . org / ns / } subscribed - strip - alarms ( omit if alarms
* should not be stripped ) .
* 5. { http :// calendarserver . org / ns / } subscribed - strip - attachments ( omit if
* attachments should not be stripped ) .
* 6. { http :// calendarserver . org / ns / } source ( Must be a
* Sabre\DAV\Property\Href ) .
* 7. { http :// apple . com / ns / ical / } calendar - color
* 8. { http :// apple . com / ns / ical / } calendar - order
* 9. { urn : ietf : params : xml : ns : caldav } supported - calendar - component - set
* ( should just be an instance of
* Sabre\CalDAV\Property\SupportedCalendarComponentSet , with a bunch of
* default components ) .
*
* @ param string $principalUri
* @ return array
*/
function getSubscriptionsForUser ( $principalUri ) {
$fields = array_values ( $this -> subscriptionPropertyMap );
$fields [] = 'id' ;
$fields [] = 'uri' ;
$fields [] = 'source' ;
$fields [] = 'principaluri' ;
$fields [] = 'lastmodified' ;
$query = $this -> db -> getQueryBuilder ();
$query -> select ( $fields )
-> from ( 'calendarsubscriptions' )
-> where ( $query -> expr () -> eq ( 'principaluri' , $query -> createNamedParameter ( $principalUri )))
-> orderBy ( 'calendarorder' , 'asc' );
$stmt = $query -> execute ();
$subscriptions = [];
while ( $row = $stmt -> fetch ( \PDO :: FETCH_ASSOC )) {
$subscription = [
'id' => $row [ 'id' ],
'uri' => $row [ 'uri' ],
'principaluri' => $row [ 'principaluri' ],
'source' => $row [ 'source' ],
'lastmodified' => $row [ 'lastmodified' ],
'{' . Plugin :: NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet ([ 'VTODO' , 'VEVENT' ]),
];
foreach ( $this -> subscriptionPropertyMap as $xmlName => $dbName ) {
if ( ! is_null ( $row [ $dbName ])) {
$subscription [ $xmlName ] = $row [ $dbName ];
}
}
$subscriptions [] = $subscription ;
}
return $subscriptions ;
}
/**
* Creates a new subscription for a principal .
*
* If the creation was a success , an id must be returned that can be used to reference
* this subscription in other methods , such as updateSubscription .
*
* @ param string $principalUri
* @ param string $uri
* @ param array $properties
* @ return mixed
*/
function createSubscription ( $principalUri , $uri , array $properties ) {
if ( ! isset ( $properties [ '{http://calendarserver.org/ns/}source' ])) {
throw new Forbidden ( 'The {http://calendarserver.org/ns/}source property is required when creating subscriptions' );
}
$values = [
'principaluri' => $principalUri ,
'uri' => $uri ,
'source' => $properties [ '{http://calendarserver.org/ns/}source' ] -> getHref (),
'lastmodified' => time (),
];
2016-07-01 14:42:35 +03:00
$propertiesBoolean = [ 'striptodos' , 'stripalarms' , 'stripattachments' ];
2015-10-31 03:28:21 +03:00
2016-07-01 14:42:35 +03:00
foreach ( $this -> subscriptionPropertyMap as $xmlName => $dbName ) {
if ( array_key_exists ( $xmlName , $properties )) {
$values [ $dbName ] = $properties [ $xmlName ];
if ( in_array ( $dbName , $propertiesBoolean )) {
$values [ $dbName ] = true ;
}
2015-10-31 03:28:21 +03:00
}
}
2016-07-01 14:42:35 +03:00
$valuesToInsert = array ();
2015-10-31 03:28:21 +03:00
$query = $this -> db -> getQueryBuilder ();
2016-07-01 14:42:35 +03:00
foreach ( array_keys ( $values ) as $name ) {
$valuesToInsert [ $name ] = $query -> createNamedParameter ( $values [ $name ]);
}
2015-10-31 03:28:21 +03:00
$query -> insert ( 'calendarsubscriptions' )
2016-07-01 14:42:35 +03:00
-> values ( $valuesToInsert )
2015-10-31 03:28:21 +03:00
-> execute ();
2015-11-16 17:28:34 +03:00
return $this -> db -> lastInsertId ( '*PREFIX*calendarsubscriptions' );
2015-10-31 03:28:21 +03:00
}
/**
* Updates a subscription
*
* The list of mutations is stored in a Sabre\DAV\PropPatch object .
* To do the actual updates , you must tell this object which properties
* you ' re going to process with the handle () method .
*
* Calling the handle method is like telling the PropPatch object " I
* promise I can handle updating this property " .
*
* Read the PropPatch documentation for more info and examples .
*
* @ param mixed $subscriptionId
2016-04-19 12:33:37 +03:00
* @ param PropPatch $propPatch
2015-10-31 03:28:21 +03:00
* @ return void
*/
2016-04-19 12:33:37 +03:00
function updateSubscription ( $subscriptionId , PropPatch $propPatch ) {
2015-10-31 03:28:21 +03:00
$supportedProperties = array_keys ( $this -> subscriptionPropertyMap );
$supportedProperties [] = '{http://calendarserver.org/ns/}source' ;
$propPatch -> handle ( $supportedProperties , function ( $mutations ) use ( $subscriptionId ) {
$newValues = [];
foreach ( $mutations as $propertyName => $propertyValue ) {
2016-02-12 16:38:43 +03:00
if ( $propertyName === '{http://calendarserver.org/ns/}source' ) {
2015-10-31 03:28:21 +03:00
$newValues [ 'source' ] = $propertyValue -> getHref ();
} else {
$fieldName = $this -> subscriptionPropertyMap [ $propertyName ];
$newValues [ $fieldName ] = $propertyValue ;
}
}
$query = $this -> db -> getQueryBuilder ();
$query -> update ( 'calendarsubscriptions' )
-> set ( 'lastmodified' , $query -> createNamedParameter ( time ()));
foreach ( $newValues as $fieldName => $value ) {
$query -> set ( $fieldName , $query -> createNamedParameter ( $value ));
}
$query -> where ( $query -> expr () -> eq ( 'id' , $query -> createNamedParameter ( $subscriptionId )))
-> execute ();
return true ;
});
}
/**
* Deletes a subscription .
*
* @ param mixed $subscriptionId
* @ return void
*/
function deleteSubscription ( $subscriptionId ) {
$query = $this -> db -> getQueryBuilder ();
$query -> delete ( 'calendarsubscriptions' )
-> where ( $query -> expr () -> eq ( 'id' , $query -> createNamedParameter ( $subscriptionId )))
-> execute ();
}
/**
* Returns a single scheduling object for the inbox collection .
*
* The returned array should contain the following elements :
* * uri - A unique basename for the object . This will be used to
* construct a full uri .
* * calendardata - The iCalendar object
* * lastmodified - The last modification date . Can be an int for a unix
* timestamp , or a PHP DateTime object .
* * etag - A unique token that must change if the object changed .
* * size - The size of the object , in bytes .
*
* @ param string $principalUri
* @ param string $objectUri
* @ return array
*/
function getSchedulingObject ( $principalUri , $objectUri ) {
2015-11-16 17:49:46 +03:00
$query = $this -> db -> getQueryBuilder ();
$stmt = $query -> select ([ 'uri' , 'calendardata' , 'lastmodified' , 'etag' , 'size' ])
-> from ( 'schedulingobjects' )
-> where ( $query -> expr () -> eq ( 'principaluri' , $query -> createNamedParameter ( $principalUri )))
-> andWhere ( $query -> expr () -> eq ( 'uri' , $query -> createNamedParameter ( $objectUri )))
-> execute ();
$row = $stmt -> fetch ( \PDO :: FETCH_ASSOC );
if ( ! $row ) {
return null ;
}
return [
'uri' => $row [ 'uri' ],
'calendardata' => $row [ 'calendardata' ],
'lastmodified' => $row [ 'lastmodified' ],
'etag' => '"' . $row [ 'etag' ] . '"' ,
'size' => ( int ) $row [ 'size' ],
];
2015-10-31 03:28:21 +03:00
}
/**
* Returns all scheduling objects for the inbox collection .
*
* These objects should be returned as an array . Every item in the array
* should follow the same structure as returned from getSchedulingObject .
*
* The main difference is that 'calendardata' is optional .
*
* @ param string $principalUri
* @ return array
*/
function getSchedulingObjects ( $principalUri ) {
2015-11-16 17:49:46 +03:00
$query = $this -> db -> getQueryBuilder ();
$stmt = $query -> select ([ 'uri' , 'calendardata' , 'lastmodified' , 'etag' , 'size' ])
-> from ( 'schedulingobjects' )
-> where ( $query -> expr () -> eq ( 'principaluri' , $query -> createNamedParameter ( $principalUri )))
-> execute ();
$result = [];
foreach ( $stmt -> fetchAll ( \PDO :: FETCH_ASSOC ) as $row ) {
$result [] = [
'calendardata' => $row [ 'calendardata' ],
'uri' => $row [ 'uri' ],
'lastmodified' => $row [ 'lastmodified' ],
'etag' => '"' . $row [ 'etag' ] . '"' ,
'size' => ( int ) $row [ 'size' ],
];
}
return $result ;
2015-10-31 03:28:21 +03:00
}
/**
* Deletes a scheduling object from the inbox collection .
*
* @ param string $principalUri
* @ param string $objectUri
* @ return void
*/
function deleteSchedulingObject ( $principalUri , $objectUri ) {
2015-11-16 17:49:46 +03:00
$query = $this -> db -> getQueryBuilder ();
$query -> delete ( 'schedulingobjects' )
-> where ( $query -> expr () -> eq ( 'principaluri' , $query -> createNamedParameter ( $principalUri )))
-> andWhere ( $query -> expr () -> eq ( 'uri' , $query -> createNamedParameter ( $objectUri )))
-> execute ();
2015-10-31 03:28:21 +03:00
}
/**
* Creates a new scheduling object . This should land in a users ' inbox .
*
* @ param string $principalUri
* @ param string $objectUri
* @ param string $objectData
* @ return void
*/
function createSchedulingObject ( $principalUri , $objectUri , $objectData ) {
2015-11-16 17:49:46 +03:00
$query = $this -> db -> getQueryBuilder ();
$query -> insert ( 'schedulingobjects' )
-> values ([
'principaluri' => $query -> createNamedParameter ( $principalUri ),
'calendardata' => $query -> createNamedParameter ( $objectData ),
'uri' => $query -> createNamedParameter ( $objectUri ),
'lastmodified' => $query -> createNamedParameter ( time ()),
'etag' => $query -> createNamedParameter ( md5 ( $objectData )),
'size' => $query -> createNamedParameter ( strlen ( $objectData ))
])
-> execute ();
2015-10-31 03:28:21 +03:00
}
/**
* Adds a change record to the calendarchanges table .
*
* @ param mixed $calendarId
* @ param string $objectUri
* @ param int $operation 1 = add , 2 = modify , 3 = delete .
* @ return void
*/
protected function addChange ( $calendarId , $objectUri , $operation ) {
$stmt = $this -> db -> prepare ( 'INSERT INTO `*PREFIX*calendarchanges` (`uri`, `synctoken`, `calendarid`, `operation`) SELECT ?, `synctoken`, ?, ? FROM `*PREFIX*calendars` WHERE `id` = ?' );
$stmt -> execute ([
$objectUri ,
$calendarId ,
$operation ,
$calendarId
]);
$stmt = $this -> db -> prepare ( 'UPDATE `*PREFIX*calendars` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?' );
$stmt -> execute ([
$calendarId
]);
}
/**
* Parses some information from calendar objects , used for optimized
* calendar - queries .
*
* Returns an array with the following keys :
* * etag - An md5 checksum of the object without the quotes .
* * size - Size of the object in bytes
* * componentType - VEVENT , VTODO or VJOURNAL
* * firstOccurence
* * lastOccurence
* * uid - value of the UID property
*
* @ param string $calendarData
* @ return array
*/
2016-04-19 12:33:37 +03:00
public function getDenormalizedData ( $calendarData ) {
2015-10-31 03:28:21 +03:00
$vObject = Reader :: read ( $calendarData );
$componentType = null ;
$component = null ;
2016-04-19 12:33:37 +03:00
$firstOccurrence = null ;
$lastOccurrence = null ;
2015-10-31 03:28:21 +03:00
$uid = null ;
2016-04-19 12:33:37 +03:00
$classification = self :: CLASSIFICATION_PUBLIC ;
2015-10-31 03:28:21 +03:00
foreach ( $vObject -> getComponents () as $component ) {
if ( $component -> name !== 'VTIMEZONE' ) {
$componentType = $component -> name ;
$uid = ( string ) $component -> UID ;
break ;
}
}
if ( ! $componentType ) {
throw new \Sabre\DAV\Exception\BadRequest ( 'Calendar objects must have a VJOURNAL, VEVENT or VTODO component' );
}
2016-03-09 20:12:39 +03:00
if ( $componentType === 'VEVENT' && $component -> DTSTART ) {
2016-05-31 12:13:45 +03:00
$firstOccurrence = $component -> DTSTART -> getDateTime () -> getTimeStamp ();
2016-01-29 20:25:27 +03:00
// Finding the last occurrence is a bit harder
2015-10-31 03:28:21 +03:00
if ( ! isset ( $component -> RRULE )) {
if ( isset ( $component -> DTEND )) {
2016-04-19 12:33:37 +03:00
$lastOccurrence = $component -> DTEND -> getDateTime () -> getTimeStamp ();
2015-10-31 03:28:21 +03:00
} elseif ( isset ( $component -> DURATION )) {
$endDate = clone $component -> DTSTART -> getDateTime ();
$endDate -> add ( DateTimeParser :: parse ( $component -> DURATION -> getValue ()));
2016-04-19 12:33:37 +03:00
$lastOccurrence = $endDate -> getTimeStamp ();
2015-10-31 03:28:21 +03:00
} elseif ( ! $component -> DTSTART -> hasTime ()) {
$endDate = clone $component -> DTSTART -> getDateTime ();
$endDate -> modify ( '+1 day' );
2016-04-19 12:33:37 +03:00
$lastOccurrence = $endDate -> getTimeStamp ();
2015-10-31 03:28:21 +03:00
} else {
2016-05-31 12:13:45 +03:00
$lastOccurrence = $firstOccurrence ;
2015-10-31 03:28:21 +03:00
}
} else {
2016-04-19 12:33:37 +03:00
$it = new EventIterator ( $vObject , ( string ) $component -> UID );
2015-10-31 03:28:21 +03:00
$maxDate = new \DateTime ( self :: MAX_DATE );
if ( $it -> isInfinite ()) {
2016-10-19 11:49:43 +03:00
$lastOccurrence = $maxDate -> getTimestamp ();
2015-10-31 03:28:21 +03:00
} else {
$end = $it -> getDtEnd ();
while ( $it -> valid () && $end < $maxDate ) {
$end = $it -> getDtEnd ();
$it -> next ();
}
2016-10-19 11:49:43 +03:00
$lastOccurrence = $end -> getTimestamp ();
2015-10-31 03:28:21 +03:00
}
}
}
2016-04-19 12:33:37 +03:00
if ( $component -> CLASS ) {
$classification = CalDavBackend :: CLASSIFICATION_PRIVATE ;
switch ( $component -> CLASS -> getValue ()) {
case 'PUBLIC' :
$classification = CalDavBackend :: CLASSIFICATION_PUBLIC ;
break ;
case 'CONFIDENTIAL' :
$classification = CalDavBackend :: CLASSIFICATION_CONFIDENTIAL ;
break ;
}
}
2015-10-31 03:28:21 +03:00
return [
2016-04-19 12:33:37 +03:00
'etag' => md5 ( $calendarData ),
'size' => strlen ( $calendarData ),
'componentType' => $componentType ,
'firstOccurence' => is_null ( $firstOccurrence ) ? null : max ( 0 , $firstOccurrence ),
'lastOccurence' => $lastOccurrence ,
'uid' => $uid ,
'classification' => $classification
2015-10-31 03:28:21 +03:00
];
}
private function readBlob ( $cardData ) {
if ( is_resource ( $cardData )) {
return stream_get_contents ( $cardData );
}
return $cardData ;
}
2016-01-26 14:06:02 +03:00
2016-01-25 19:18:47 +03:00
/**
* @ param IShareable $shareable
* @ param array $add
* @ param array $remove
*/
2016-01-26 14:06:02 +03:00
public function updateShares ( $shareable , $add , $remove ) {
2016-10-13 16:34:26 +03:00
$calendarId = $shareable -> getResourceId ();
$this -> dispatcher -> dispatch ( '\OCA\DAV\CalDAV\CalDavBackend::updateShares' , new GenericEvent (
'\OCA\DAV\CalDAV\CalDavBackend::updateShares' ,
[
'calendarId' => $calendarId ,
'calendarData' => $this -> getCalendarById ( $calendarId ),
'shares' => $this -> getShares ( $calendarId ),
'add' => $add ,
'remove' => $remove ,
]));
2016-01-26 14:06:02 +03:00
$this -> sharingBackend -> updateShares ( $shareable , $add , $remove );
}
2016-01-25 19:18:47 +03:00
/**
* @ param int $resourceId
* @ return array
*/
2016-01-26 14:06:02 +03:00
public function getShares ( $resourceId ) {
return $this -> sharingBackend -> getShares ( $resourceId );
}
2016-07-06 13:19:46 +03:00
/**
2016-08-01 11:51:11 +03:00
* @ param boolean $value
* @ param \OCA\DAV\CalDAV\Calendar $calendar
2016-09-03 11:52:05 +03:00
* @ return string | null
2016-08-01 11:51:11 +03:00
*/
2016-07-31 21:18:35 +03:00
public function setPublishStatus ( $value , $calendar ) {
$query = $this -> db -> getQueryBuilder ();
if ( $value ) {
2016-09-03 11:52:05 +03:00
$publicUri = $this -> random -> generate ( 16 , ISecureRandom :: CHAR_UPPER . ISecureRandom :: CHAR_DIGITS );
2016-07-31 21:18:35 +03:00
$query -> insert ( 'dav_shares' )
-> values ([
'principaluri' => $query -> createNamedParameter ( $calendar -> getPrincipalURI ()),
'type' => $query -> createNamedParameter ( 'calendar' ),
'access' => $query -> createNamedParameter ( self :: ACCESS_PUBLIC ),
2016-08-01 17:44:28 +03:00
'resourceid' => $query -> createNamedParameter ( $calendar -> getResourceId ()),
2016-09-03 11:52:05 +03:00
'publicuri' => $query -> createNamedParameter ( $publicUri )
2016-07-31 21:18:35 +03:00
]);
2016-09-03 11:52:05 +03:00
$query -> execute ();
return $publicUri ;
2016-07-31 21:18:35 +03:00
}
2016-09-03 11:52:05 +03:00
$query -> delete ( 'dav_shares' )
-> where ( $query -> expr () -> eq ( 'resourceid' , $query -> createNamedParameter ( $calendar -> getResourceId ())))
-> andWhere ( $query -> expr () -> eq ( 'access' , $query -> createNamedParameter ( self :: ACCESS_PUBLIC )));
2016-07-31 21:18:35 +03:00
$query -> execute ();
2016-09-03 11:52:05 +03:00
return null ;
2016-07-31 21:18:35 +03:00
}
2016-07-06 13:19:46 +03:00
/**
* @ param \OCA\DAV\CalDAV\Calendar $calendar
2016-09-15 18:01:35 +03:00
* @ return mixed
2016-07-06 13:19:46 +03:00
*/
public function getPublishStatus ( $calendar ) {
2016-09-14 12:34:21 +03:00
$query = $this -> db -> getQueryBuilder ();
$result = $query -> select ( 'publicuri' )
-> from ( 'dav_shares' )
-> where ( $query -> expr () -> eq ( 'resourceid' , $query -> createNamedParameter ( $calendar -> getResourceId ())))
-> andWhere ( $query -> expr () -> eq ( 'access' , $query -> createNamedParameter ( self :: ACCESS_PUBLIC )))
-> execute ();
$row = $result -> fetch ();
$result -> closeCursor ();
2016-09-15 18:01:35 +03:00
return $row ? reset ( $row ) : false ;
2016-09-14 12:34:21 +03:00
}
2016-01-25 19:18:47 +03:00
/**
* @ param int $resourceId
* @ param array $acl
* @ return array
*/
public function applyShareAcl ( $resourceId , $acl ) {
return $this -> sharingBackend -> applyShareAcl ( $resourceId , $acl );
2016-01-26 14:06:02 +03:00
}
2016-02-03 17:43:45 +03:00
2017-03-25 13:56:40 +03:00
/**
* update properties table
*
* @ param int $calendarId
* @ param string $objectUri
* @ param string $calendarData
*/
2017-04-25 12:55:31 +03:00
public function updateProperties ( $calendarId , $objectUri , $calendarData ) {
2017-03-25 13:56:40 +03:00
$objectId = $this -> getCalendarObjectId ( $calendarId , $objectUri );
2017-04-25 17:42:41 +03:00
try {
$vCalendar = $this -> readCalendarData ( $calendarData );
} catch ( \Exception $ex ) {
return ;
}
2017-03-25 13:56:40 +03:00
$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' ];
}
2016-02-12 16:38:43 +03:00
private function convertPrincipal ( $principalUri , $toV2 ) {
if ( $this -> principalBackend -> getPrincipalPrefix () === 'principals' ) {
list (, $name ) = URLUtil :: splitPath ( $principalUri );
if ( $toV2 === true ) {
return " principals/users/ $name " ;
}
return " principals/ $name " ;
}
return $principalUri ;
}
2017-04-19 17:18:44 +03:00
private function addOwnerPrincipal ( & $calendarInfo ) {
$ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin :: NS_OWNCLOUD . '}owner-principal' ;
$displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin :: NS_NEXTCLOUD . '}owner-displayname' ;
if ( isset ( $calendarInfo [ $ownerPrincipalKey ])) {
$uri = $calendarInfo [ $ownerPrincipalKey ];
} else {
$uri = $calendarInfo [ 'principaluri' ];
}
$principalInformation = $this -> principalBackend -> getPrincipalByPath ( $uri );
if ( isset ( $principalInformation [ '{DAV:}displayname' ])) {
$calendarInfo [ $displaynameKey ] = $principalInformation [ '{DAV:}displayname' ];
}
}
2015-10-31 03:28:21 +03:00
}