1<?php 2 3namespace Sabre\DAV; 4 5/** 6 * This class holds all the information about a PROPFIND request. 7 * 8 * It contains the type of PROPFIND request, which properties were requested 9 * and also the returned items. 10 */ 11class PropFind { 12 13 /** 14 * A normal propfind 15 */ 16 const NORMAL = 0; 17 18 /** 19 * An allprops request. 20 * 21 * While this was originally intended for instructing the server to really 22 * fetch every property, because it was used so often and it's so heavy 23 * this turned into a small list of default properties after a while. 24 * 25 * So 'all properties' now means a hardcoded list. 26 */ 27 const ALLPROPS = 1; 28 29 /** 30 * A propname request. This just returns a list of properties that are 31 * defined on a node, without their values. 32 */ 33 const PROPNAME = 2; 34 35 /** 36 * Creates the PROPFIND object 37 * 38 * @param string $path 39 * @param array $properties 40 * @param int $depth 41 * @param int $requestType 42 */ 43 function __construct($path, array $properties, $depth = 0, $requestType = self::NORMAL) { 44 45 $this->path = $path; 46 $this->properties = $properties; 47 $this->depth = $depth; 48 $this->requestType = $requestType; 49 50 if ($requestType === self::ALLPROPS) { 51 $this->properties = [ 52 '{DAV:}getlastmodified', 53 '{DAV:}getcontentlength', 54 '{DAV:}resourcetype', 55 '{DAV:}quota-used-bytes', 56 '{DAV:}quota-available-bytes', 57 '{DAV:}getetag', 58 '{DAV:}getcontenttype', 59 ]; 60 } 61 62 foreach ($this->properties as $propertyName) { 63 64 // Seeding properties with 404's. 65 $this->result[$propertyName] = [404, null]; 66 67 } 68 $this->itemsLeft = count($this->result); 69 70 } 71 72 /** 73 * Handles a specific property. 74 * 75 * This method checks wether the specified property was requested in this 76 * PROPFIND request, and if so, it will call the callback and use the 77 * return value for it's value. 78 * 79 * Example: 80 * 81 * $propFind->handle('{DAV:}displayname', function() { 82 * return 'hello'; 83 * }); 84 * 85 * Note that handle will only work the first time. If null is returned, the 86 * value is ignored. 87 * 88 * It's also possible to not pass a callback, but immediately pass a value 89 * 90 * @param string $propertyName 91 * @param mixed $valueOrCallBack 92 * @return void 93 */ 94 function handle($propertyName, $valueOrCallBack) { 95 96 if ($this->itemsLeft && isset($this->result[$propertyName]) && $this->result[$propertyName][0] === 404) { 97 if (is_callable($valueOrCallBack)) { 98 $value = $valueOrCallBack(); 99 } else { 100 $value = $valueOrCallBack; 101 } 102 if (!is_null($value)) { 103 $this->itemsLeft--; 104 $this->result[$propertyName] = [200, $value]; 105 } 106 } 107 108 } 109 110 /** 111 * Sets the value of the property 112 * 113 * If status is not supplied, the status will default to 200 for non-null 114 * properties, and 404 for null properties. 115 * 116 * @param string $propertyName 117 * @param mixed $value 118 * @param int $status 119 * @return void 120 */ 121 function set($propertyName, $value, $status = null) { 122 123 if (is_null($status)) { 124 $status = is_null($value) ? 404 : 200; 125 } 126 // If this is an ALLPROPS request and the property is 127 // unknown, add it to the result; else ignore it: 128 if (!isset($this->result[$propertyName])) { 129 if ($this->requestType === self::ALLPROPS) { 130 $this->result[$propertyName] = [$status, $value]; 131 } 132 return; 133 } 134 if ($status !== 404 && $this->result[$propertyName][0] === 404) { 135 $this->itemsLeft--; 136 } elseif ($status === 404 && $this->result[$propertyName][0] !== 404) { 137 $this->itemsLeft++; 138 } 139 $this->result[$propertyName] = [$status, $value]; 140 141 } 142 143 /** 144 * Returns the current value for a property. 145 * 146 * @param string $propertyName 147 * @return mixed 148 */ 149 function get($propertyName) { 150 151 return isset($this->result[$propertyName]) ? $this->result[$propertyName][1] : null; 152 153 } 154 155 /** 156 * Returns the current status code for a property name. 157 * 158 * If the property does not appear in the list of requested properties, 159 * null will be returned. 160 * 161 * @param string $propertyName 162 * @return int|null 163 */ 164 function getStatus($propertyName) { 165 166 return isset($this->result[$propertyName]) ? $this->result[$propertyName][0] : null; 167 168 } 169 170 /** 171 * Updates the path for this PROPFIND. 172 * 173 * @param string $path 174 * @return void 175 */ 176 function setPath($path) { 177 178 $this->path = $path; 179 180 } 181 182 /** 183 * Returns the path this PROPFIND request is for. 184 * 185 * @return string 186 */ 187 function getPath() { 188 189 return $this->path; 190 191 } 192 193 /** 194 * Returns the depth of this propfind request. 195 * 196 * @return int 197 */ 198 function getDepth() { 199 200 return $this->depth; 201 202 } 203 204 /** 205 * Updates the depth of this propfind request. 206 * 207 * @param int $depth 208 * @return void 209 */ 210 function setDepth($depth) { 211 212 $this->depth = $depth; 213 214 } 215 216 /** 217 * Returns all propertynames that have a 404 status, and thus don't have a 218 * value yet. 219 * 220 * @return array 221 */ 222 function get404Properties() { 223 224 if ($this->itemsLeft === 0) { 225 return []; 226 } 227 $result = []; 228 foreach ($this->result as $propertyName => $stuff) { 229 if ($stuff[0] === 404) { 230 $result[] = $propertyName; 231 } 232 } 233 return $result; 234 235 } 236 237 /** 238 * Returns the full list of requested properties. 239 * 240 * This returns just their names, not a status or value. 241 * 242 * @return array 243 */ 244 function getRequestedProperties() { 245 246 return $this->properties; 247 248 } 249 250 /** 251 * Returns true if this was an '{DAV:}allprops' request. 252 * 253 * @return bool 254 */ 255 function isAllProps() { 256 257 return $this->requestType === self::ALLPROPS; 258 259 } 260 261 /** 262 * Returns a result array that's often used in multistatus responses. 263 * 264 * The array uses status codes as keys, and property names and value pairs 265 * as the value of the top array.. such as : 266 * 267 * [ 268 * 200 => [ '{DAV:}displayname' => 'foo' ], 269 * ] 270 * 271 * @return array 272 */ 273 function getResultForMultiStatus() { 274 275 $r = [ 276 200 => [], 277 404 => [], 278 ]; 279 foreach ($this->result as $propertyName => $info) { 280 if (!isset($r[$info[0]])) { 281 $r[$info[0]] = [$propertyName => $info[1]]; 282 } else { 283 $r[$info[0]][$propertyName] = $info[1]; 284 } 285 } 286 // Removing the 404's for multi-status requests. 287 if ($this->requestType === self::ALLPROPS) unset($r[404]); 288 return $r; 289 290 } 291 292 /** 293 * The path that we're fetching properties for. 294 * 295 * @var string 296 */ 297 protected $path; 298 299 /** 300 * The Depth of the request. 301 * 302 * 0 means only the current item. 1 means the current item + its children. 303 * It can also be DEPTH_INFINITY if this is enabled in the server. 304 * 305 * @var int 306 */ 307 protected $depth = 0; 308 309 /** 310 * The type of request. See the TYPE constants 311 */ 312 protected $requestType; 313 314 /** 315 * A list of requested properties 316 * 317 * @var array 318 */ 319 protected $properties = []; 320 321 /** 322 * The result of the operation. 323 * 324 * The keys in this array are property names. 325 * The values are an array with two elements: the http status code and then 326 * optionally a value. 327 * 328 * Example: 329 * 330 * [ 331 * "{DAV:}owner" : [404], 332 * "{DAV:}displayname" : [200, "Admin"] 333 * ] 334 * 335 * @var array 336 */ 337 protected $result = []; 338 339 /** 340 * This is used as an internal counter for the number of properties that do 341 * not yet have a value. 342 * 343 * @var int 344 */ 345 protected $itemsLeft; 346 347} 348