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