1*a1a3b679SAndreas Boehler<?php 2*a1a3b679SAndreas Boehler 3*a1a3b679SAndreas Boehlernamespace Sabre\DAV; 4*a1a3b679SAndreas Boehler 5*a1a3b679SAndreas Boehleruse UnexpectedValueException; 6*a1a3b679SAndreas Boehler 7*a1a3b679SAndreas Boehler/** 8*a1a3b679SAndreas Boehler * This class represents a set of properties that are going to be updated. 9*a1a3b679SAndreas Boehler * 10*a1a3b679SAndreas Boehler * Usually this is simply a PROPPATCH request, but it can also be used for 11*a1a3b679SAndreas Boehler * internal updates. 12*a1a3b679SAndreas Boehler * 13*a1a3b679SAndreas Boehler * Property updates must always be atomic. This means that a property update 14*a1a3b679SAndreas Boehler * must either completely succeed, or completely fail. 15*a1a3b679SAndreas Boehler * 16*a1a3b679SAndreas Boehler * @copyright Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/). 17*a1a3b679SAndreas Boehler * @author Evert Pot (http://evertpot.com/) 18*a1a3b679SAndreas Boehler * @license http://sabre.io/license/ Modified BSD License 19*a1a3b679SAndreas Boehler */ 20*a1a3b679SAndreas Boehlerclass PropPatch { 21*a1a3b679SAndreas Boehler 22*a1a3b679SAndreas Boehler /** 23*a1a3b679SAndreas Boehler * Properties that are being updated. 24*a1a3b679SAndreas Boehler * 25*a1a3b679SAndreas Boehler * This is a key-value list. If the value is null, the property is supposed 26*a1a3b679SAndreas Boehler * to be deleted. 27*a1a3b679SAndreas Boehler * 28*a1a3b679SAndreas Boehler * @var array 29*a1a3b679SAndreas Boehler */ 30*a1a3b679SAndreas Boehler protected $mutations; 31*a1a3b679SAndreas Boehler 32*a1a3b679SAndreas Boehler /** 33*a1a3b679SAndreas Boehler * A list of properties and the result of the update. The result is in the 34*a1a3b679SAndreas Boehler * form of a HTTP status code. 35*a1a3b679SAndreas Boehler * 36*a1a3b679SAndreas Boehler * @var array 37*a1a3b679SAndreas Boehler */ 38*a1a3b679SAndreas Boehler protected $result = []; 39*a1a3b679SAndreas Boehler 40*a1a3b679SAndreas Boehler /** 41*a1a3b679SAndreas Boehler * This is the list of callbacks when we're performing the actual update. 42*a1a3b679SAndreas Boehler * 43*a1a3b679SAndreas Boehler * @var array 44*a1a3b679SAndreas Boehler */ 45*a1a3b679SAndreas Boehler protected $propertyUpdateCallbacks = []; 46*a1a3b679SAndreas Boehler 47*a1a3b679SAndreas Boehler /** 48*a1a3b679SAndreas Boehler * This property will be set to true if the operation failed. 49*a1a3b679SAndreas Boehler * 50*a1a3b679SAndreas Boehler * @var bool 51*a1a3b679SAndreas Boehler */ 52*a1a3b679SAndreas Boehler protected $failed = false; 53*a1a3b679SAndreas Boehler 54*a1a3b679SAndreas Boehler /** 55*a1a3b679SAndreas Boehler * Constructor 56*a1a3b679SAndreas Boehler * 57*a1a3b679SAndreas Boehler * @param array $mutations A list of updates 58*a1a3b679SAndreas Boehler */ 59*a1a3b679SAndreas Boehler function __construct(array $mutations) { 60*a1a3b679SAndreas Boehler 61*a1a3b679SAndreas Boehler $this->mutations = $mutations; 62*a1a3b679SAndreas Boehler 63*a1a3b679SAndreas Boehler } 64*a1a3b679SAndreas Boehler 65*a1a3b679SAndreas Boehler /** 66*a1a3b679SAndreas Boehler * Call this function if you wish to handle updating certain properties. 67*a1a3b679SAndreas Boehler * For instance, your class may be responsible for handling updates for the 68*a1a3b679SAndreas Boehler * {DAV:}displayname property. 69*a1a3b679SAndreas Boehler * 70*a1a3b679SAndreas Boehler * In that case, call this method with the first argument 71*a1a3b679SAndreas Boehler * "{DAV:}displayname" and a second argument that's a method that does the 72*a1a3b679SAndreas Boehler * actual updating. 73*a1a3b679SAndreas Boehler * 74*a1a3b679SAndreas Boehler * It's possible to specify more than one property. 75*a1a3b679SAndreas Boehler * 76*a1a3b679SAndreas Boehler * @param string|string[] $properties 77*a1a3b679SAndreas Boehler * @param callable $callback 78*a1a3b679SAndreas Boehler * @return void 79*a1a3b679SAndreas Boehler */ 80*a1a3b679SAndreas Boehler function handle($properties, callable $callback) { 81*a1a3b679SAndreas Boehler 82*a1a3b679SAndreas Boehler $usedProperties = []; 83*a1a3b679SAndreas Boehler foreach ((array)$properties as $propertyName) { 84*a1a3b679SAndreas Boehler 85*a1a3b679SAndreas Boehler if (array_key_exists($propertyName, $this->mutations) && !isset($this->result[$propertyName])) { 86*a1a3b679SAndreas Boehler 87*a1a3b679SAndreas Boehler $usedProperties[] = $propertyName; 88*a1a3b679SAndreas Boehler // HTTP Accepted 89*a1a3b679SAndreas Boehler $this->result[$propertyName] = 202; 90*a1a3b679SAndreas Boehler } 91*a1a3b679SAndreas Boehler 92*a1a3b679SAndreas Boehler } 93*a1a3b679SAndreas Boehler 94*a1a3b679SAndreas Boehler // Only registering if there's any unhandled properties. 95*a1a3b679SAndreas Boehler if (!$usedProperties) { 96*a1a3b679SAndreas Boehler return; 97*a1a3b679SAndreas Boehler } 98*a1a3b679SAndreas Boehler $this->propertyUpdateCallbacks[] = [ 99*a1a3b679SAndreas Boehler // If the original argument to this method was a string, we need 100*a1a3b679SAndreas Boehler // to also make sure that it stays that way, so the commit function 101*a1a3b679SAndreas Boehler // knows how to format the arguments to the callback. 102*a1a3b679SAndreas Boehler is_string($properties) ? $properties : $usedProperties, 103*a1a3b679SAndreas Boehler $callback 104*a1a3b679SAndreas Boehler ]; 105*a1a3b679SAndreas Boehler 106*a1a3b679SAndreas Boehler } 107*a1a3b679SAndreas Boehler 108*a1a3b679SAndreas Boehler /** 109*a1a3b679SAndreas Boehler * Call this function if you wish to handle _all_ properties that haven't 110*a1a3b679SAndreas Boehler * been handled by anything else yet. Note that you effectively claim with 111*a1a3b679SAndreas Boehler * this that you promise to process _all_ properties that are coming in. 112*a1a3b679SAndreas Boehler * 113*a1a3b679SAndreas Boehler * @param callable $callback 114*a1a3b679SAndreas Boehler * @return void 115*a1a3b679SAndreas Boehler */ 116*a1a3b679SAndreas Boehler function handleRemaining(callable $callback) { 117*a1a3b679SAndreas Boehler 118*a1a3b679SAndreas Boehler $properties = $this->getRemainingMutations(); 119*a1a3b679SAndreas Boehler if (!$properties) { 120*a1a3b679SAndreas Boehler // Nothing to do, don't register callback 121*a1a3b679SAndreas Boehler return; 122*a1a3b679SAndreas Boehler } 123*a1a3b679SAndreas Boehler 124*a1a3b679SAndreas Boehler foreach ($properties as $propertyName) { 125*a1a3b679SAndreas Boehler // HTTP Accepted 126*a1a3b679SAndreas Boehler $this->result[$propertyName] = 202; 127*a1a3b679SAndreas Boehler 128*a1a3b679SAndreas Boehler $this->propertyUpdateCallbacks[] = [ 129*a1a3b679SAndreas Boehler $properties, 130*a1a3b679SAndreas Boehler $callback 131*a1a3b679SAndreas Boehler ]; 132*a1a3b679SAndreas Boehler } 133*a1a3b679SAndreas Boehler 134*a1a3b679SAndreas Boehler } 135*a1a3b679SAndreas Boehler 136*a1a3b679SAndreas Boehler /** 137*a1a3b679SAndreas Boehler * Sets the result code for one or more properties. 138*a1a3b679SAndreas Boehler * 139*a1a3b679SAndreas Boehler * @param string|string[] $properties 140*a1a3b679SAndreas Boehler * @param int $resultCode 141*a1a3b679SAndreas Boehler * @return void 142*a1a3b679SAndreas Boehler */ 143*a1a3b679SAndreas Boehler function setResultCode($properties, $resultCode) { 144*a1a3b679SAndreas Boehler 145*a1a3b679SAndreas Boehler foreach ((array)$properties as $propertyName) { 146*a1a3b679SAndreas Boehler $this->result[$propertyName] = $resultCode; 147*a1a3b679SAndreas Boehler } 148*a1a3b679SAndreas Boehler 149*a1a3b679SAndreas Boehler if ($resultCode >= 400) { 150*a1a3b679SAndreas Boehler $this->failed = true; 151*a1a3b679SAndreas Boehler } 152*a1a3b679SAndreas Boehler 153*a1a3b679SAndreas Boehler } 154*a1a3b679SAndreas Boehler 155*a1a3b679SAndreas Boehler /** 156*a1a3b679SAndreas Boehler * Sets the result code for all properties that did not have a result yet. 157*a1a3b679SAndreas Boehler * 158*a1a3b679SAndreas Boehler * @param int $resultCode 159*a1a3b679SAndreas Boehler * @return void 160*a1a3b679SAndreas Boehler */ 161*a1a3b679SAndreas Boehler function setRemainingResultCode($resultCode) { 162*a1a3b679SAndreas Boehler 163*a1a3b679SAndreas Boehler $this->setResultCode( 164*a1a3b679SAndreas Boehler $this->getRemainingMutations(), 165*a1a3b679SAndreas Boehler $resultCode 166*a1a3b679SAndreas Boehler ); 167*a1a3b679SAndreas Boehler 168*a1a3b679SAndreas Boehler } 169*a1a3b679SAndreas Boehler 170*a1a3b679SAndreas Boehler /** 171*a1a3b679SAndreas Boehler * Returns the list of properties that don't have a result code yet. 172*a1a3b679SAndreas Boehler * 173*a1a3b679SAndreas Boehler * This method returns a list of property names, but not its values. 174*a1a3b679SAndreas Boehler * 175*a1a3b679SAndreas Boehler * @return string[] 176*a1a3b679SAndreas Boehler */ 177*a1a3b679SAndreas Boehler function getRemainingMutations() { 178*a1a3b679SAndreas Boehler 179*a1a3b679SAndreas Boehler $remaining = []; 180*a1a3b679SAndreas Boehler foreach ($this->mutations as $propertyName => $propValue) { 181*a1a3b679SAndreas Boehler if (!isset($this->result[$propertyName])) { 182*a1a3b679SAndreas Boehler $remaining[] = $propertyName; 183*a1a3b679SAndreas Boehler } 184*a1a3b679SAndreas Boehler } 185*a1a3b679SAndreas Boehler 186*a1a3b679SAndreas Boehler return $remaining; 187*a1a3b679SAndreas Boehler 188*a1a3b679SAndreas Boehler } 189*a1a3b679SAndreas Boehler 190*a1a3b679SAndreas Boehler /** 191*a1a3b679SAndreas Boehler * Returns the list of properties that don't have a result code yet. 192*a1a3b679SAndreas Boehler * 193*a1a3b679SAndreas Boehler * This method returns list of properties and their values. 194*a1a3b679SAndreas Boehler * 195*a1a3b679SAndreas Boehler * @return array 196*a1a3b679SAndreas Boehler */ 197*a1a3b679SAndreas Boehler function getRemainingValues() { 198*a1a3b679SAndreas Boehler 199*a1a3b679SAndreas Boehler $remaining = []; 200*a1a3b679SAndreas Boehler foreach ($this->mutations as $propertyName => $propValue) { 201*a1a3b679SAndreas Boehler if (!isset($this->result[$propertyName])) { 202*a1a3b679SAndreas Boehler $remaining[$propertyName] = $propValue; 203*a1a3b679SAndreas Boehler } 204*a1a3b679SAndreas Boehler } 205*a1a3b679SAndreas Boehler 206*a1a3b679SAndreas Boehler return $remaining; 207*a1a3b679SAndreas Boehler 208*a1a3b679SAndreas Boehler } 209*a1a3b679SAndreas Boehler 210*a1a3b679SAndreas Boehler /** 211*a1a3b679SAndreas Boehler * Performs the actual update, and calls all callbacks. 212*a1a3b679SAndreas Boehler * 213*a1a3b679SAndreas Boehler * This method returns true or false depending on if the operation was 214*a1a3b679SAndreas Boehler * successful. 215*a1a3b679SAndreas Boehler * 216*a1a3b679SAndreas Boehler * @return bool 217*a1a3b679SAndreas Boehler */ 218*a1a3b679SAndreas Boehler function commit() { 219*a1a3b679SAndreas Boehler 220*a1a3b679SAndreas Boehler // First we validate if every property has a handler 221*a1a3b679SAndreas Boehler foreach ($this->mutations as $propertyName => $value) { 222*a1a3b679SAndreas Boehler 223*a1a3b679SAndreas Boehler if (!isset($this->result[$propertyName])) { 224*a1a3b679SAndreas Boehler $this->failed = true; 225*a1a3b679SAndreas Boehler $this->result[$propertyName] = 403; 226*a1a3b679SAndreas Boehler } 227*a1a3b679SAndreas Boehler 228*a1a3b679SAndreas Boehler } 229*a1a3b679SAndreas Boehler 230*a1a3b679SAndreas Boehler foreach ($this->propertyUpdateCallbacks as $callbackInfo) { 231*a1a3b679SAndreas Boehler 232*a1a3b679SAndreas Boehler if ($this->failed) { 233*a1a3b679SAndreas Boehler break; 234*a1a3b679SAndreas Boehler } 235*a1a3b679SAndreas Boehler if (is_string($callbackInfo[0])) { 236*a1a3b679SAndreas Boehler $this->doCallbackSingleProp($callbackInfo[0], $callbackInfo[1]); 237*a1a3b679SAndreas Boehler } else { 238*a1a3b679SAndreas Boehler $this->doCallbackMultiProp($callbackInfo[0], $callbackInfo[1]); 239*a1a3b679SAndreas Boehler } 240*a1a3b679SAndreas Boehler 241*a1a3b679SAndreas Boehler } 242*a1a3b679SAndreas Boehler 243*a1a3b679SAndreas Boehler /** 244*a1a3b679SAndreas Boehler * If anywhere in this operation updating a property failed, we must 245*a1a3b679SAndreas Boehler * update all other properties accordingly. 246*a1a3b679SAndreas Boehler */ 247*a1a3b679SAndreas Boehler if ($this->failed) { 248*a1a3b679SAndreas Boehler 249*a1a3b679SAndreas Boehler foreach ($this->result as $propertyName => $status) { 250*a1a3b679SAndreas Boehler if ($status === 202) { 251*a1a3b679SAndreas Boehler // Failed dependency 252*a1a3b679SAndreas Boehler $this->result[$propertyName] = 424; 253*a1a3b679SAndreas Boehler } 254*a1a3b679SAndreas Boehler } 255*a1a3b679SAndreas Boehler 256*a1a3b679SAndreas Boehler } 257*a1a3b679SAndreas Boehler 258*a1a3b679SAndreas Boehler return !$this->failed; 259*a1a3b679SAndreas Boehler 260*a1a3b679SAndreas Boehler } 261*a1a3b679SAndreas Boehler 262*a1a3b679SAndreas Boehler /** 263*a1a3b679SAndreas Boehler * Executes a property callback with the single-property syntax. 264*a1a3b679SAndreas Boehler * 265*a1a3b679SAndreas Boehler * @param string $propertyName 266*a1a3b679SAndreas Boehler * @param callable $callback 267*a1a3b679SAndreas Boehler * @return void 268*a1a3b679SAndreas Boehler */ 269*a1a3b679SAndreas Boehler private function doCallBackSingleProp($propertyName, callable $callback) { 270*a1a3b679SAndreas Boehler 271*a1a3b679SAndreas Boehler $result = $callback($this->mutations[$propertyName]); 272*a1a3b679SAndreas Boehler if (is_bool($result)) { 273*a1a3b679SAndreas Boehler if ($result) { 274*a1a3b679SAndreas Boehler if (is_null($this->mutations[$propertyName])) { 275*a1a3b679SAndreas Boehler // Delete 276*a1a3b679SAndreas Boehler $result = 204; 277*a1a3b679SAndreas Boehler } else { 278*a1a3b679SAndreas Boehler // Update 279*a1a3b679SAndreas Boehler $result = 200; 280*a1a3b679SAndreas Boehler } 281*a1a3b679SAndreas Boehler } else { 282*a1a3b679SAndreas Boehler // Fail 283*a1a3b679SAndreas Boehler $result = 403; 284*a1a3b679SAndreas Boehler } 285*a1a3b679SAndreas Boehler } 286*a1a3b679SAndreas Boehler if (!is_int($result)) { 287*a1a3b679SAndreas Boehler throw new UnexpectedValueException('A callback sent to handle() did not return an int or a bool'); 288*a1a3b679SAndreas Boehler } 289*a1a3b679SAndreas Boehler $this->result[$propertyName] = $result; 290*a1a3b679SAndreas Boehler if ($result >= 400) { 291*a1a3b679SAndreas Boehler $this->failed = true; 292*a1a3b679SAndreas Boehler } 293*a1a3b679SAndreas Boehler 294*a1a3b679SAndreas Boehler } 295*a1a3b679SAndreas Boehler 296*a1a3b679SAndreas Boehler /** 297*a1a3b679SAndreas Boehler * Executes a property callback with the multi-property syntax. 298*a1a3b679SAndreas Boehler * 299*a1a3b679SAndreas Boehler * @param array $propertyList 300*a1a3b679SAndreas Boehler * @param callable $callback 301*a1a3b679SAndreas Boehler * @return void 302*a1a3b679SAndreas Boehler */ 303*a1a3b679SAndreas Boehler private function doCallBackMultiProp(array $propertyList, callable $callback) { 304*a1a3b679SAndreas Boehler 305*a1a3b679SAndreas Boehler $argument = []; 306*a1a3b679SAndreas Boehler foreach ($propertyList as $propertyName) { 307*a1a3b679SAndreas Boehler $argument[$propertyName] = $this->mutations[$propertyName]; 308*a1a3b679SAndreas Boehler } 309*a1a3b679SAndreas Boehler 310*a1a3b679SAndreas Boehler $result = $callback($argument); 311*a1a3b679SAndreas Boehler 312*a1a3b679SAndreas Boehler if (is_array($result)) { 313*a1a3b679SAndreas Boehler foreach ($propertyList as $propertyName) { 314*a1a3b679SAndreas Boehler if (!isset($result[$propertyName])) { 315*a1a3b679SAndreas Boehler $resultCode = 500; 316*a1a3b679SAndreas Boehler } else { 317*a1a3b679SAndreas Boehler $resultCode = $result[$propertyName]; 318*a1a3b679SAndreas Boehler } 319*a1a3b679SAndreas Boehler if ($resultCode >= 400) { 320*a1a3b679SAndreas Boehler $this->failed = true; 321*a1a3b679SAndreas Boehler } 322*a1a3b679SAndreas Boehler $this->result[$propertyName] = $resultCode; 323*a1a3b679SAndreas Boehler 324*a1a3b679SAndreas Boehler } 325*a1a3b679SAndreas Boehler } elseif ($result === true) { 326*a1a3b679SAndreas Boehler 327*a1a3b679SAndreas Boehler // Success 328*a1a3b679SAndreas Boehler foreach ($argument as $propertyName => $propertyValue) { 329*a1a3b679SAndreas Boehler $this->result[$propertyName] = is_null($propertyValue) ? 204 : 200; 330*a1a3b679SAndreas Boehler } 331*a1a3b679SAndreas Boehler 332*a1a3b679SAndreas Boehler } elseif ($result === false) { 333*a1a3b679SAndreas Boehler // Fail :( 334*a1a3b679SAndreas Boehler $this->failed = true; 335*a1a3b679SAndreas Boehler foreach ($propertyList as $propertyName) { 336*a1a3b679SAndreas Boehler $this->result[$propertyName] = 403; 337*a1a3b679SAndreas Boehler } 338*a1a3b679SAndreas Boehler } else { 339*a1a3b679SAndreas Boehler throw new UnexpectedValueException('A callback sent to handle() did not return an array or a bool'); 340*a1a3b679SAndreas Boehler } 341*a1a3b679SAndreas Boehler 342*a1a3b679SAndreas Boehler } 343*a1a3b679SAndreas Boehler 344*a1a3b679SAndreas Boehler /** 345*a1a3b679SAndreas Boehler * Returns the result of the operation. 346*a1a3b679SAndreas Boehler * 347*a1a3b679SAndreas Boehler * @return array 348*a1a3b679SAndreas Boehler */ 349*a1a3b679SAndreas Boehler function getResult() { 350*a1a3b679SAndreas Boehler 351*a1a3b679SAndreas Boehler return $this->result; 352*a1a3b679SAndreas Boehler 353*a1a3b679SAndreas Boehler } 354*a1a3b679SAndreas Boehler 355*a1a3b679SAndreas Boehler /** 356*a1a3b679SAndreas Boehler * Returns the full list of mutations 357*a1a3b679SAndreas Boehler * 358*a1a3b679SAndreas Boehler * @return array 359*a1a3b679SAndreas Boehler */ 360*a1a3b679SAndreas Boehler function getMutations() { 361*a1a3b679SAndreas Boehler 362*a1a3b679SAndreas Boehler return $this->mutations; 363*a1a3b679SAndreas Boehler 364*a1a3b679SAndreas Boehler } 365*a1a3b679SAndreas Boehler 366*a1a3b679SAndreas Boehler} 367