1<?php 2 3namespace Sabre\CalDAV; 4 5use Sabre\DAV; 6use Sabre\DAV\Exception\NotFound; 7use Sabre\DAV\MkCol; 8use Sabre\DAVACL; 9use Sabre\HTTP\URLUtil; 10 11/** 12 * The CalendarHome represents a node that is usually in a users' 13 * calendar-homeset. 14 * 15 * It contains all the users' calendars, and can optionally contain a 16 * notifications collection, calendar subscriptions, a users' inbox, and a 17 * users' outbox. 18 * 19 * @copyright Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/). 20 * @author Evert Pot (http://evertpot.com/) 21 * @license http://sabre.io/license/ Modified BSD License 22 */ 23class CalendarHome implements DAV\IExtendedCollection, DAVACL\IACL { 24 25 /** 26 * CalDAV backend 27 * 28 * @var Sabre\CalDAV\Backend\BackendInterface 29 */ 30 protected $caldavBackend; 31 32 /** 33 * Principal information 34 * 35 * @var array 36 */ 37 protected $principalInfo; 38 39 /** 40 * Constructor 41 * 42 * @param Backend\BackendInterface $caldavBackend 43 * @param mixed $userUri 44 */ 45 function __construct(Backend\BackendInterface $caldavBackend, $principalInfo) { 46 47 $this->caldavBackend = $caldavBackend; 48 $this->principalInfo = $principalInfo; 49 50 } 51 52 /** 53 * Returns the name of this object 54 * 55 * @return string 56 */ 57 function getName() { 58 59 list(, $name) = URLUtil::splitPath($this->principalInfo['uri']); 60 return $name; 61 62 } 63 64 /** 65 * Updates the name of this object 66 * 67 * @param string $name 68 * @return void 69 */ 70 function setName($name) { 71 72 throw new DAV\Exception\Forbidden(); 73 74 } 75 76 /** 77 * Deletes this object 78 * 79 * @return void 80 */ 81 function delete() { 82 83 throw new DAV\Exception\Forbidden(); 84 85 } 86 87 /** 88 * Returns the last modification date 89 * 90 * @return int 91 */ 92 function getLastModified() { 93 94 return null; 95 96 } 97 98 /** 99 * Creates a new file under this object. 100 * 101 * This is currently not allowed 102 * 103 * @param string $filename 104 * @param resource $data 105 * @return void 106 */ 107 function createFile($filename, $data = null) { 108 109 throw new DAV\Exception\MethodNotAllowed('Creating new files in this collection is not supported'); 110 111 } 112 113 /** 114 * Creates a new directory under this object. 115 * 116 * This is currently not allowed. 117 * 118 * @param string $filename 119 * @return void 120 */ 121 function createDirectory($filename) { 122 123 throw new DAV\Exception\MethodNotAllowed('Creating new collections in this collection is not supported'); 124 125 } 126 127 /** 128 * Returns a single calendar, by name 129 * 130 * @param string $name 131 * @return Calendar 132 */ 133 function getChild($name) { 134 135 // Special nodes 136 if ($name === 'inbox' && $this->caldavBackend instanceof Backend\SchedulingSupport) { 137 return new Schedule\Inbox($this->caldavBackend, $this->principalInfo['uri']); 138 } 139 if ($name === 'outbox' && $this->caldavBackend instanceof Backend\SchedulingSupport) { 140 return new Schedule\Outbox($this->principalInfo['uri']); 141 } 142 if ($name === 'notifications' && $this->caldavBackend instanceof Backend\NotificationSupport) { 143 return new Notifications\Collection($this->caldavBackend, $this->principalInfo['uri']); 144 } 145 146 // Calendars 147 foreach ($this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']) as $calendar) { 148 if ($calendar['uri'] === $name) { 149 if ($this->caldavBackend instanceof Backend\SharingSupport) { 150 if (isset($calendar['{http://calendarserver.org/ns/}shared-url'])) { 151 return new SharedCalendar($this->caldavBackend, $calendar); 152 } else { 153 return new ShareableCalendar($this->caldavBackend, $calendar); 154 } 155 } else { 156 return new Calendar($this->caldavBackend, $calendar); 157 } 158 } 159 } 160 161 if ($this->caldavBackend instanceof Backend\SubscriptionSupport) { 162 foreach ($this->caldavBackend->getSubscriptionsForUser($this->principalInfo['uri']) as $subscription) { 163 if ($subscription['uri'] === $name) { 164 return new Subscriptions\Subscription($this->caldavBackend, $subscription); 165 } 166 } 167 168 } 169 170 throw new NotFound('Node with name \'' . $name . '\' could not be found'); 171 172 } 173 174 /** 175 * Checks if a calendar exists. 176 * 177 * @param string $name 178 * @return bool 179 */ 180 function childExists($name) { 181 182 try { 183 return !!$this->getChild($name); 184 } catch (NotFound $e) { 185 return false; 186 } 187 188 } 189 190 /** 191 * Returns a list of calendars 192 * 193 * @return array 194 */ 195 function getChildren() { 196 197 $calendars = $this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']); 198 $objs = []; 199 foreach ($calendars as $calendar) { 200 if ($this->caldavBackend instanceof Backend\SharingSupport) { 201 if (isset($calendar['{http://calendarserver.org/ns/}shared-url'])) { 202 $objs[] = new SharedCalendar($this->caldavBackend, $calendar); 203 } else { 204 $objs[] = new ShareableCalendar($this->caldavBackend, $calendar); 205 } 206 } else { 207 $objs[] = new Calendar($this->caldavBackend, $calendar); 208 } 209 } 210 211 if ($this->caldavBackend instanceof Backend\SchedulingSupport) { 212 $objs[] = new Schedule\Inbox($this->caldavBackend, $this->principalInfo['uri']); 213 $objs[] = new Schedule\Outbox($this->principalInfo['uri']); 214 } 215 216 // We're adding a notifications node, if it's supported by the backend. 217 if ($this->caldavBackend instanceof Backend\NotificationSupport) { 218 $objs[] = new Notifications\Collection($this->caldavBackend, $this->principalInfo['uri']); 219 } 220 221 // If the backend supports subscriptions, we'll add those as well, 222 if ($this->caldavBackend instanceof Backend\SubscriptionSupport) { 223 foreach ($this->caldavBackend->getSubscriptionsForUser($this->principalInfo['uri']) as $subscription) { 224 $objs[] = new Subscriptions\Subscription($this->caldavBackend, $subscription); 225 } 226 } 227 228 return $objs; 229 230 } 231 232 /** 233 * Creates a new calendar or subscription. 234 * 235 * @param string $name 236 * @param MkCol $mkCol 237 * @throws DAV\Exception\InvalidResourceType 238 * @return void 239 */ 240 function createExtendedCollection($name, MkCol $mkCol) { 241 242 $isCalendar = false; 243 $isSubscription = false; 244 foreach ($mkCol->getResourceType() as $rt) { 245 switch ($rt) { 246 case '{DAV:}collection' : 247 case '{http://calendarserver.org/ns/}shared-owner' : 248 // ignore 249 break; 250 case '{urn:ietf:params:xml:ns:caldav}calendar' : 251 $isCalendar = true; 252 break; 253 case '{http://calendarserver.org/ns/}subscribed' : 254 $isSubscription = true; 255 break; 256 default : 257 throw new DAV\Exception\InvalidResourceType('Unknown resourceType: ' . $rt); 258 } 259 } 260 261 $properties = $mkCol->getRemainingValues(); 262 $mkCol->setRemainingResultCode(201); 263 264 if ($isSubscription) { 265 if (!$this->caldavBackend instanceof Backend\SubscriptionSupport) { 266 throw new DAV\Exception\InvalidResourceType('This backend does not support subscriptions'); 267 } 268 $this->caldavBackend->createSubscription($this->principalInfo['uri'], $name, $properties); 269 270 } elseif ($isCalendar) { 271 $this->caldavBackend->createCalendar($this->principalInfo['uri'], $name, $properties); 272 273 } else { 274 throw new DAV\Exception\InvalidResourceType('You can only create calendars and subscriptions in this collection'); 275 276 } 277 278 } 279 280 /** 281 * Returns the owner principal 282 * 283 * This must be a url to a principal, or null if there's no owner 284 * 285 * @return string|null 286 */ 287 function getOwner() { 288 289 return $this->principalInfo['uri']; 290 291 } 292 293 /** 294 * Returns a group principal 295 * 296 * This must be a url to a principal, or null if there's no owner 297 * 298 * @return string|null 299 */ 300 function getGroup() { 301 302 return null; 303 304 } 305 306 /** 307 * Returns a list of ACE's for this node. 308 * 309 * Each ACE has the following properties: 310 * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are 311 * currently the only supported privileges 312 * * 'principal', a url to the principal who owns the node 313 * * 'protected' (optional), indicating that this ACE is not allowed to 314 * be updated. 315 * 316 * @return array 317 */ 318 function getACL() { 319 320 return [ 321 [ 322 'privilege' => '{DAV:}read', 323 'principal' => $this->principalInfo['uri'], 324 'protected' => true, 325 ], 326 [ 327 'privilege' => '{DAV:}write', 328 'principal' => $this->principalInfo['uri'], 329 'protected' => true, 330 ], 331 [ 332 'privilege' => '{DAV:}read', 333 'principal' => $this->principalInfo['uri'] . '/calendar-proxy-write', 334 'protected' => true, 335 ], 336 [ 337 'privilege' => '{DAV:}write', 338 'principal' => $this->principalInfo['uri'] . '/calendar-proxy-write', 339 'protected' => true, 340 ], 341 [ 342 'privilege' => '{DAV:}read', 343 'principal' => $this->principalInfo['uri'] . '/calendar-proxy-read', 344 'protected' => true, 345 ], 346 347 ]; 348 349 } 350 351 /** 352 * Updates the ACL 353 * 354 * This method will receive a list of new ACE's. 355 * 356 * @param array $acl 357 * @return void 358 */ 359 function setACL(array $acl) { 360 361 throw new DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported'); 362 363 } 364 365 /** 366 * Returns the list of supported privileges for this node. 367 * 368 * The returned data structure is a list of nested privileges. 369 * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple 370 * standard structure. 371 * 372 * If null is returned from this method, the default privilege set is used, 373 * which is fine for most common usecases. 374 * 375 * @return array|null 376 */ 377 function getSupportedPrivilegeSet() { 378 379 return null; 380 381 } 382 383 /** 384 * This method is called when a user replied to a request to share. 385 * 386 * This method should return the url of the newly created calendar if the 387 * share was accepted. 388 * 389 * @param string href The sharee who is replying (often a mailto: address) 390 * @param int status One of the SharingPlugin::STATUS_* constants 391 * @param string $calendarUri The url to the calendar thats being shared 392 * @param string $inReplyTo The unique id this message is a response to 393 * @param string $summary A description of the reply 394 * @return null|string 395 */ 396 function shareReply($href, $status, $calendarUri, $inReplyTo, $summary = null) { 397 398 if (!$this->caldavBackend instanceof Backend\SharingSupport) { 399 throw new DAV\Exception\NotImplemented('Sharing support is not implemented by this backend.'); 400 } 401 402 return $this->caldavBackend->shareReply($href, $status, $calendarUri, $inReplyTo, $summary); 403 404 } 405 406 /** 407 * Searches through all of a users calendars and calendar objects to find 408 * an object with a specific UID. 409 * 410 * This method should return the path to this object, relative to the 411 * calendar home, so this path usually only contains two parts: 412 * 413 * calendarpath/objectpath.ics 414 * 415 * If the uid is not found, return null. 416 * 417 * This method should only consider * objects that the principal owns, so 418 * any calendars owned by other principals that also appear in this 419 * collection should be ignored. 420 * 421 * @param string $uid 422 * @return string|null 423 */ 424 function getCalendarObjectByUID($uid) { 425 426 return $this->caldavBackend->getCalendarObjectByUID($this->principalInfo['uri'], $uid); 427 428 } 429 430} 431