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) 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 use DAVACL\ACLTrait; 20 21 /** 22 * This is an array with addressbook information 23 * 24 * @var array 25 */ 26 protected $addressBookInfo; 27 28 /** 29 * CardDAV backend 30 * 31 * @var Backend\BackendInterface 32 */ 33 protected $carddavBackend; 34 35 /** 36 * Constructor 37 * 38 * @param Backend\BackendInterface $carddavBackend 39 * @param array $addressBookInfo 40 */ 41 function __construct(Backend\BackendInterface $carddavBackend, array $addressBookInfo) { 42 43 $this->carddavBackend = $carddavBackend; 44 $this->addressBookInfo = $addressBookInfo; 45 46 } 47 48 /** 49 * Returns the name of the addressbook 50 * 51 * @return string 52 */ 53 function getName() { 54 55 return $this->addressBookInfo['uri']; 56 57 } 58 59 /** 60 * Returns a card 61 * 62 * @param string $name 63 * @return Card 64 */ 65 function getChild($name) { 66 67 $obj = $this->carddavBackend->getCard($this->addressBookInfo['id'], $name); 68 if (!$obj) throw new DAV\Exception\NotFound('Card not found'); 69 return new Card($this->carddavBackend, $this->addressBookInfo, $obj); 70 71 } 72 73 /** 74 * Returns the full list of cards 75 * 76 * @return array 77 */ 78 function getChildren() { 79 80 $objs = $this->carddavBackend->getCards($this->addressBookInfo['id']); 81 $children = []; 82 foreach ($objs as $obj) { 83 $obj['acl'] = $this->getChildACL(); 84 $children[] = new Card($this->carddavBackend, $this->addressBookInfo, $obj); 85 } 86 return $children; 87 88 } 89 90 /** 91 * This method receives a list of paths in it's first argument. 92 * It must return an array with Node objects. 93 * 94 * If any children are not found, you do not have to return them. 95 * 96 * @param string[] $paths 97 * @return array 98 */ 99 function getMultipleChildren(array $paths) { 100 101 $objs = $this->carddavBackend->getMultipleCards($this->addressBookInfo['id'], $paths); 102 $children = []; 103 foreach ($objs as $obj) { 104 $obj['acl'] = $this->getChildACL(); 105 $children[] = new Card($this->carddavBackend, $this->addressBookInfo, $obj); 106 } 107 return $children; 108 109 } 110 111 /** 112 * Creates a new directory 113 * 114 * We actually block this, as subdirectories are not allowed in addressbooks. 115 * 116 * @param string $name 117 * @return void 118 */ 119 function createDirectory($name) { 120 121 throw new DAV\Exception\MethodNotAllowed('Creating collections in addressbooks is not allowed'); 122 123 } 124 125 /** 126 * Creates a new file 127 * 128 * The contents of the new file must be a valid VCARD. 129 * 130 * This method may return an ETag. 131 * 132 * @param string $name 133 * @param resource $vcardData 134 * @return string|null 135 */ 136 function createFile($name, $vcardData = null) { 137 138 if (is_resource($vcardData)) { 139 $vcardData = stream_get_contents($vcardData); 140 } 141 // Converting to UTF-8, if needed 142 $vcardData = DAV\StringUtil::ensureUTF8($vcardData); 143 144 return $this->carddavBackend->createCard($this->addressBookInfo['id'], $name, $vcardData); 145 146 } 147 148 /** 149 * Deletes the entire addressbook. 150 * 151 * @return void 152 */ 153 function delete() { 154 155 $this->carddavBackend->deleteAddressBook($this->addressBookInfo['id']); 156 157 } 158 159 /** 160 * Renames the addressbook 161 * 162 * @param string $newName 163 * @return void 164 */ 165 function setName($newName) { 166 167 throw new DAV\Exception\MethodNotAllowed('Renaming addressbooks is not yet supported'); 168 169 } 170 171 /** 172 * Returns the last modification date as a unix timestamp. 173 * 174 * @return void 175 */ 176 function getLastModified() { 177 178 return null; 179 180 } 181 182 /** 183 * Updates properties on this node. 184 * 185 * This method received a PropPatch object, which contains all the 186 * information about the update. 187 * 188 * To update specific properties, call the 'handle' method on this object. 189 * Read the PropPatch documentation for more information. 190 * 191 * @param DAV\PropPatch $propPatch 192 * @return void 193 */ 194 function propPatch(DAV\PropPatch $propPatch) { 195 196 return $this->carddavBackend->updateAddressBook($this->addressBookInfo['id'], $propPatch); 197 198 } 199 200 /** 201 * Returns a list of properties for this nodes. 202 * 203 * The properties list is a list of propertynames the client requested, 204 * encoded in clark-notation {xmlnamespace}tagname 205 * 206 * If the array is empty, it means 'all properties' were requested. 207 * 208 * @param array $properties 209 * @return array 210 */ 211 function getProperties($properties) { 212 213 $response = []; 214 foreach ($properties as $propertyName) { 215 216 if (isset($this->addressBookInfo[$propertyName])) { 217 218 $response[$propertyName] = $this->addressBookInfo[$propertyName]; 219 220 } 221 222 } 223 224 return $response; 225 226 } 227 228 /** 229 * Returns the owner principal 230 * 231 * This must be a url to a principal, or null if there's no owner 232 * 233 * @return string|null 234 */ 235 function getOwner() { 236 237 return $this->addressBookInfo['principaluri']; 238 239 } 240 241 242 /** 243 * This method returns the ACL's for card nodes in this address book. 244 * The result of this method automatically gets passed to the 245 * card nodes in this address book. 246 * 247 * @return array 248 */ 249 function getChildACL() { 250 251 return [ 252 [ 253 'privilege' => '{DAV:}all', 254 'principal' => $this->getOwner(), 255 'protected' => true, 256 ], 257 ]; 258 259 } 260 261 262 /** 263 * This method returns the current sync-token for this collection. 264 * This can be any string. 265 * 266 * If null is returned from this function, the plugin assumes there's no 267 * sync information available. 268 * 269 * @return string|null 270 */ 271 function getSyncToken() { 272 273 if ( 274 $this->carddavBackend instanceof Backend\SyncSupport && 275 isset($this->addressBookInfo['{DAV:}sync-token']) 276 ) { 277 return $this->addressBookInfo['{DAV:}sync-token']; 278 } 279 if ( 280 $this->carddavBackend instanceof Backend\SyncSupport && 281 isset($this->addressBookInfo['{http://sabredav.org/ns}sync-token']) 282 ) { 283 return $this->addressBookInfo['{http://sabredav.org/ns}sync-token']; 284 } 285 286 } 287 288 /** 289 * The getChanges method returns all the changes that have happened, since 290 * the specified syncToken and the current collection. 291 * 292 * This function should return an array, such as the following: 293 * 294 * [ 295 * 'syncToken' => 'The current synctoken', 296 * 'added' => [ 297 * 'new.txt', 298 * ], 299 * 'modified' => [ 300 * 'modified.txt', 301 * ], 302 * 'deleted' => [ 303 * 'foo.php.bak', 304 * 'old.txt' 305 * ] 306 * ]; 307 * 308 * The syncToken property should reflect the *current* syncToken of the 309 * collection, as reported getSyncToken(). This is needed here too, to 310 * ensure the operation is atomic. 311 * 312 * If the syncToken is specified as null, this is an initial sync, and all 313 * members should be reported. 314 * 315 * The modified property is an array of nodenames that have changed since 316 * the last token. 317 * 318 * The deleted property is an array with nodenames, that have been deleted 319 * from collection. 320 * 321 * The second argument is basically the 'depth' of the report. If it's 1, 322 * you only have to report changes that happened only directly in immediate 323 * descendants. If it's 2, it should also include changes from the nodes 324 * below the child collections. (grandchildren) 325 * 326 * The third (optional) argument allows a client to specify how many 327 * results should be returned at most. If the limit is not specified, it 328 * should be treated as infinite. 329 * 330 * If the limit (infinite or not) is higher than you're willing to return, 331 * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception. 332 * 333 * If the syncToken is expired (due to data cleanup) or unknown, you must 334 * return null. 335 * 336 * The limit is 'suggestive'. You are free to ignore it. 337 * 338 * @param string $syncToken 339 * @param int $syncLevel 340 * @param int $limit 341 * @return array 342 */ 343 function getChanges($syncToken, $syncLevel, $limit = null) { 344 345 if (!$this->carddavBackend instanceof Backend\SyncSupport) { 346 return null; 347 } 348 349 return $this->carddavBackend->getChangesForAddressBook( 350 $this->addressBookInfo['id'], 351 $syncToken, 352 $syncLevel, 353 $limit 354 ); 355 356 } 357} 358