xref: /plugin/davcal/vendor/sabre/dav/lib/DAV/PropPatch.php (revision a1a3b6794e0e143a4a8b51d3185ce2d339be61ab)
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