1<?php 2 3namespace Sabre\CalDAV; 4 5use Sabre\DAV; 6use Sabre\DAV\PropPatch; 7use Sabre\DAVACL; 8 9/** 10 * This object represents a CalDAV calendar. 11 * 12 * A calendar can contain multiple TODO and or Events. These are represented 13 * as \Sabre\CalDAV\CalendarObject objects. 14 * 15 * @copyright Copyright (C) fruux GmbH (https://fruux.com/) 16 * @author Evert Pot (http://evertpot.com/) 17 * @license http://sabre.io/license/ Modified BSD License 18 */ 19class Calendar implements ICalendar, DAV\IProperties, DAV\Sync\ISyncCollection, DAV\IMultiGet { 20 21 use DAVACL\ACLTrait; 22 23 /** 24 * This is an array with calendar information 25 * 26 * @var array 27 */ 28 protected $calendarInfo; 29 30 /** 31 * CalDAV backend 32 * 33 * @var Backend\BackendInterface 34 */ 35 protected $caldavBackend; 36 37 /** 38 * Constructor 39 * 40 * @param Backend\BackendInterface $caldavBackend 41 * @param array $calendarInfo 42 */ 43 function __construct(Backend\BackendInterface $caldavBackend, $calendarInfo) { 44 45 $this->caldavBackend = $caldavBackend; 46 $this->calendarInfo = $calendarInfo; 47 48 } 49 50 /** 51 * Returns the name of the calendar 52 * 53 * @return string 54 */ 55 function getName() { 56 57 return $this->calendarInfo['uri']; 58 59 } 60 61 /** 62 * Updates properties on this node. 63 * 64 * This method received a PropPatch object, which contains all the 65 * information about the update. 66 * 67 * To update specific properties, call the 'handle' method on this object. 68 * Read the PropPatch documentation for more information. 69 * 70 * @param PropPatch $propPatch 71 * @return void 72 */ 73 function propPatch(PropPatch $propPatch) { 74 75 return $this->caldavBackend->updateCalendar($this->calendarInfo['id'], $propPatch); 76 77 } 78 79 /** 80 * Returns the list of properties 81 * 82 * @param array $requestedProperties 83 * @return array 84 */ 85 function getProperties($requestedProperties) { 86 87 $response = []; 88 89 foreach ($this->calendarInfo as $propName => $propValue) { 90 91 if (!is_null($propValue) && $propName[0] === '{') 92 $response[$propName] = $this->calendarInfo[$propName]; 93 94 } 95 return $response; 96 97 } 98 99 /** 100 * Returns a calendar object 101 * 102 * The contained calendar objects are for example Events or Todo's. 103 * 104 * @param string $name 105 * @return \Sabre\CalDAV\ICalendarObject 106 */ 107 function getChild($name) { 108 109 $obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $name); 110 111 if (!$obj) throw new DAV\Exception\NotFound('Calendar object not found'); 112 113 $obj['acl'] = $this->getChildACL(); 114 115 return new CalendarObject($this->caldavBackend, $this->calendarInfo, $obj); 116 117 } 118 119 /** 120 * Returns the full list of calendar objects 121 * 122 * @return array 123 */ 124 function getChildren() { 125 126 $objs = $this->caldavBackend->getCalendarObjects($this->calendarInfo['id']); 127 $children = []; 128 foreach ($objs as $obj) { 129 $obj['acl'] = $this->getChildACL(); 130 $children[] = new CalendarObject($this->caldavBackend, $this->calendarInfo, $obj); 131 } 132 return $children; 133 134 } 135 136 /** 137 * This method receives a list of paths in it's first argument. 138 * It must return an array with Node objects. 139 * 140 * If any children are not found, you do not have to return them. 141 * 142 * @param string[] $paths 143 * @return array 144 */ 145 function getMultipleChildren(array $paths) { 146 147 $objs = $this->caldavBackend->getMultipleCalendarObjects($this->calendarInfo['id'], $paths); 148 $children = []; 149 foreach ($objs as $obj) { 150 $obj['acl'] = $this->getChildACL(); 151 $children[] = new CalendarObject($this->caldavBackend, $this->calendarInfo, $obj); 152 } 153 return $children; 154 155 } 156 157 /** 158 * Checks if a child-node exists. 159 * 160 * @param string $name 161 * @return bool 162 */ 163 function childExists($name) { 164 165 $obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $name); 166 if (!$obj) 167 return false; 168 else 169 return true; 170 171 } 172 173 /** 174 * Creates a new directory 175 * 176 * We actually block this, as subdirectories are not allowed in calendars. 177 * 178 * @param string $name 179 * @return void 180 */ 181 function createDirectory($name) { 182 183 throw new DAV\Exception\MethodNotAllowed('Creating collections in calendar objects is not allowed'); 184 185 } 186 187 /** 188 * Creates a new file 189 * 190 * The contents of the new file must be a valid ICalendar string. 191 * 192 * @param string $name 193 * @param resource $calendarData 194 * @return string|null 195 */ 196 function createFile($name, $calendarData = null) { 197 198 if (is_resource($calendarData)) { 199 $calendarData = stream_get_contents($calendarData); 200 } 201 return $this->caldavBackend->createCalendarObject($this->calendarInfo['id'], $name, $calendarData); 202 203 } 204 205 /** 206 * Deletes the calendar. 207 * 208 * @return void 209 */ 210 function delete() { 211 212 $this->caldavBackend->deleteCalendar($this->calendarInfo['id']); 213 214 } 215 216 /** 217 * Renames the calendar. Note that most calendars use the 218 * {DAV:}displayname to display a name to display a name. 219 * 220 * @param string $newName 221 * @return void 222 */ 223 function setName($newName) { 224 225 throw new DAV\Exception\MethodNotAllowed('Renaming calendars is not yet supported'); 226 227 } 228 229 /** 230 * Returns the last modification date as a unix timestamp. 231 * 232 * @return null 233 */ 234 function getLastModified() { 235 236 return null; 237 238 } 239 240 /** 241 * Returns the owner principal 242 * 243 * This must be a url to a principal, or null if there's no owner 244 * 245 * @return string|null 246 */ 247 function getOwner() { 248 249 return $this->calendarInfo['principaluri']; 250 251 } 252 253 /** 254 * Returns a list of ACE's for this node. 255 * 256 * Each ACE has the following properties: 257 * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are 258 * currently the only supported privileges 259 * * 'principal', a url to the principal who owns the node 260 * * 'protected' (optional), indicating that this ACE is not allowed to 261 * be updated. 262 * 263 * @return array 264 */ 265 function getACL() { 266 267 $acl = [ 268 [ 269 'privilege' => '{DAV:}read', 270 'principal' => $this->getOwner(), 271 'protected' => true, 272 ], 273 [ 274 'privilege' => '{DAV:}read', 275 'principal' => $this->getOwner() . '/calendar-proxy-write', 276 'protected' => true, 277 ], 278 [ 279 'privilege' => '{DAV:}read', 280 'principal' => $this->getOwner() . '/calendar-proxy-read', 281 'protected' => true, 282 ], 283 [ 284 'privilege' => '{' . Plugin::NS_CALDAV . '}read-free-busy', 285 'principal' => '{DAV:}authenticated', 286 'protected' => true, 287 ], 288 289 ]; 290 if (empty($this->calendarInfo['{http://sabredav.org/ns}read-only'])) { 291 $acl[] = [ 292 'privilege' => '{DAV:}write', 293 'principal' => $this->getOwner(), 294 'protected' => true, 295 ]; 296 $acl[] = [ 297 'privilege' => '{DAV:}write', 298 'principal' => $this->getOwner() . '/calendar-proxy-write', 299 'protected' => true, 300 ]; 301 } 302 303 return $acl; 304 305 } 306 307 /** 308 * This method returns the ACL's for calendar objects in this calendar. 309 * The result of this method automatically gets passed to the 310 * calendar-object nodes in the calendar. 311 * 312 * @return array 313 */ 314 function getChildACL() { 315 316 $acl = [ 317 [ 318 'privilege' => '{DAV:}read', 319 'principal' => $this->getOwner(), 320 'protected' => true, 321 ], 322 323 [ 324 'privilege' => '{DAV:}read', 325 'principal' => $this->getOwner() . '/calendar-proxy-write', 326 'protected' => true, 327 ], 328 [ 329 'privilege' => '{DAV:}read', 330 'principal' => $this->getOwner() . '/calendar-proxy-read', 331 'protected' => true, 332 ], 333 334 ]; 335 if (empty($this->calendarInfo['{http://sabredav.org/ns}read-only'])) { 336 $acl[] = [ 337 'privilege' => '{DAV:}write', 338 'principal' => $this->getOwner(), 339 'protected' => true, 340 ]; 341 $acl[] = [ 342 'privilege' => '{DAV:}write', 343 'principal' => $this->getOwner() . '/calendar-proxy-write', 344 'protected' => true, 345 ]; 346 347 } 348 return $acl; 349 350 } 351 352 353 /** 354 * Performs a calendar-query on the contents of this calendar. 355 * 356 * The calendar-query is defined in RFC4791 : CalDAV. Using the 357 * calendar-query it is possible for a client to request a specific set of 358 * object, based on contents of iCalendar properties, date-ranges and 359 * iCalendar component types (VTODO, VEVENT). 360 * 361 * This method should just return a list of (relative) urls that match this 362 * query. 363 * 364 * The list of filters are specified as an array. The exact array is 365 * documented by Sabre\CalDAV\CalendarQueryParser. 366 * 367 * @param array $filters 368 * @return array 369 */ 370 function calendarQuery(array $filters) { 371 372 return $this->caldavBackend->calendarQuery($this->calendarInfo['id'], $filters); 373 374 } 375 376 /** 377 * This method returns the current sync-token for this collection. 378 * This can be any string. 379 * 380 * If null is returned from this function, the plugin assumes there's no 381 * sync information available. 382 * 383 * @return string|null 384 */ 385 function getSyncToken() { 386 387 if ( 388 $this->caldavBackend instanceof Backend\SyncSupport && 389 isset($this->calendarInfo['{DAV:}sync-token']) 390 ) { 391 return $this->calendarInfo['{DAV:}sync-token']; 392 } 393 if ( 394 $this->caldavBackend instanceof Backend\SyncSupport && 395 isset($this->calendarInfo['{http://sabredav.org/ns}sync-token']) 396 ) { 397 return $this->calendarInfo['{http://sabredav.org/ns}sync-token']; 398 } 399 400 } 401 402 /** 403 * The getChanges method returns all the changes that have happened, since 404 * the specified syncToken and the current collection. 405 * 406 * This function should return an array, such as the following: 407 * 408 * [ 409 * 'syncToken' => 'The current synctoken', 410 * 'added' => [ 411 * 'new.txt', 412 * ], 413 * 'modified' => [ 414 * 'modified.txt', 415 * ], 416 * 'deleted' => [ 417 * 'foo.php.bak', 418 * 'old.txt' 419 * ] 420 * ]; 421 * 422 * The syncToken property should reflect the *current* syncToken of the 423 * collection, as reported getSyncToken(). This is needed here too, to 424 * ensure the operation is atomic. 425 * 426 * If the syncToken is specified as null, this is an initial sync, and all 427 * members should be reported. 428 * 429 * The modified property is an array of nodenames that have changed since 430 * the last token. 431 * 432 * The deleted property is an array with nodenames, that have been deleted 433 * from collection. 434 * 435 * The second argument is basically the 'depth' of the report. If it's 1, 436 * you only have to report changes that happened only directly in immediate 437 * descendants. If it's 2, it should also include changes from the nodes 438 * below the child collections. (grandchildren) 439 * 440 * The third (optional) argument allows a client to specify how many 441 * results should be returned at most. If the limit is not specified, it 442 * should be treated as infinite. 443 * 444 * If the limit (infinite or not) is higher than you're willing to return, 445 * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception. 446 * 447 * If the syncToken is expired (due to data cleanup) or unknown, you must 448 * return null. 449 * 450 * The limit is 'suggestive'. You are free to ignore it. 451 * 452 * @param string $syncToken 453 * @param int $syncLevel 454 * @param int $limit 455 * @return array 456 */ 457 function getChanges($syncToken, $syncLevel, $limit = null) { 458 459 if (!$this->caldavBackend instanceof Backend\SyncSupport) { 460 return null; 461 } 462 463 return $this->caldavBackend->getChangesForCalendar( 464 $this->calendarInfo['id'], 465 $syncToken, 466 $syncLevel, 467 $limit 468 ); 469 470 } 471 472} 473