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