Completed
Branch develop (37f7b7)
by
unknown
24:41
created
htdocs/includes/sabre/sabre/dav/lib/DAVACL/Plugin.php 2 patches
Indentation   +1499 added lines, -1499 removed lines patch added patch discarded remove patch
@@ -32,1484 +32,1484 @@  discard block
 block discarded – undo
32 32
  */
33 33
 class Plugin extends DAV\ServerPlugin
34 34
 {
35
-    /**
36
-     * Recursion constants.
37
-     *
38
-     * This only checks the base node
39
-     */
40
-    const R_PARENT = 1;
41
-
42
-    /**
43
-     * Recursion constants.
44
-     *
45
-     * This checks every node in the tree
46
-     */
47
-    const R_RECURSIVE = 2;
48
-
49
-    /**
50
-     * Recursion constants.
51
-     *
52
-     * This checks every parentnode in the tree, but not leaf-nodes.
53
-     */
54
-    const R_RECURSIVEPARENTS = 3;
55
-
56
-    /**
57
-     * Reference to server object.
58
-     *
59
-     * @var DAV\Server
60
-     */
61
-    protected $server;
62
-
63
-    /**
64
-     * List of urls containing principal collections.
65
-     * Modify this if your principals are located elsewhere.
66
-     *
67
-     * @var array
68
-     */
69
-    public $principalCollectionSet = [
70
-        'principals',
71
-    ];
72
-
73
-    /**
74
-     * By default nodes that are inaccessible by the user, can still be seen
75
-     * in directory listings (PROPFIND on parent with Depth: 1).
76
-     *
77
-     * In certain cases it's desirable to hide inaccessible nodes. Setting this
78
-     * to true will cause these nodes to be hidden from directory listings.
79
-     *
80
-     * @var bool
81
-     */
82
-    public $hideNodesFromListings = false;
83
-
84
-    /**
85
-     * This list of properties are the properties a client can search on using
86
-     * the {DAV:}principal-property-search report.
87
-     *
88
-     * The keys are the property names, values are descriptions.
89
-     *
90
-     * @var array
91
-     */
92
-    public $principalSearchPropertySet = [
93
-        '{DAV:}displayname' => 'Display name',
94
-        '{http://sabredav.org/ns}email-address' => 'Email address',
95
-    ];
96
-
97
-    /**
98
-     * Any principal uri's added here, will automatically be added to the list
99
-     * of ACL's. They will effectively receive {DAV:}all privileges, as a
100
-     * protected privilege.
101
-     *
102
-     * @var array
103
-     */
104
-    public $adminPrincipals = [];
105
-
106
-    /**
107
-     * The ACL plugin allows privileges to be assigned to users that are not
108
-     * logged in. To facilitate that, it modifies the auth plugin's behavior
109
-     * to only require login when a privileged operation was denied.
110
-     *
111
-     * Unauthenticated access can be considered a security concern, so it's
112
-     * possible to turn this feature off to harden the server's security.
113
-     *
114
-     * @var bool
115
-     */
116
-    public $allowUnauthenticatedAccess = true;
117
-
118
-    /**
119
-     * Returns a list of features added by this plugin.
120
-     *
121
-     * This list is used in the response of a HTTP OPTIONS request.
122
-     *
123
-     * @return array
124
-     */
125
-    public function getFeatures()
126
-    {
127
-        return ['access-control', 'calendarserver-principal-property-search'];
128
-    }
129
-
130
-    /**
131
-     * Returns a list of available methods for a given url.
132
-     *
133
-     * @param string $uri
134
-     *
135
-     * @return array
136
-     */
137
-    public function getMethods($uri)
138
-    {
139
-        return ['ACL'];
140
-    }
141
-
142
-    /**
143
-     * Returns a plugin name.
144
-     *
145
-     * Using this name other plugins will be able to access other plugins
146
-     * using Sabre\DAV\Server::getPlugin
147
-     *
148
-     * @return string
149
-     */
150
-    public function getPluginName()
151
-    {
152
-        return 'acl';
153
-    }
154
-
155
-    /**
156
-     * Returns a list of reports this plugin supports.
157
-     *
158
-     * This will be used in the {DAV:}supported-report-set property.
159
-     * Note that you still need to subscribe to the 'report' event to actually
160
-     * implement them
161
-     *
162
-     * @param string $uri
163
-     *
164
-     * @return array
165
-     */
166
-    public function getSupportedReportSet($uri)
167
-    {
168
-        return [
169
-            '{DAV:}expand-property',
170
-            '{DAV:}principal-match',
171
-            '{DAV:}principal-property-search',
172
-            '{DAV:}principal-search-property-set',
173
-        ];
174
-    }
175
-
176
-    /**
177
-     * Checks if the current user has the specified privilege(s).
178
-     *
179
-     * You can specify a single privilege, or a list of privileges.
180
-     * This method will throw an exception if the privilege is not available
181
-     * and return true otherwise.
182
-     *
183
-     * @param string       $uri
184
-     * @param array|string $privileges
185
-     * @param int          $recursion
186
-     * @param bool         $throwExceptions if set to false, this method won't throw exceptions
187
-     *
188
-     * @throws NeedPrivileges
189
-     * @throws NotAuthenticated
190
-     *
191
-     * @return bool
192
-     */
193
-    public function checkPrivileges($uri, $privileges, $recursion = self::R_PARENT, $throwExceptions = true)
194
-    {
195
-        if (!is_array($privileges)) {
196
-            $privileges = [$privileges];
197
-        }
198
-
199
-        $acl = $this->getCurrentUserPrivilegeSet($uri);
200
-
201
-        $failed = [];
202
-        foreach ($privileges as $priv) {
203
-            if (!in_array($priv, $acl)) {
204
-                $failed[] = $priv;
205
-            }
206
-        }
207
-
208
-        if ($failed) {
209
-            if ($this->allowUnauthenticatedAccess && is_null($this->getCurrentUserPrincipal())) {
210
-                // We are not authenticated. Kicking in the Auth plugin.
211
-                $authPlugin = $this->server->getPlugin('auth');
212
-                $reasons = $authPlugin->getLoginFailedReasons();
213
-                $authPlugin->challenge(
214
-                    $this->server->httpRequest,
215
-                    $this->server->httpResponse
216
-                );
217
-                throw new NotAuthenticated(implode(', ', $reasons).'. Login was needed for privilege: '.implode(', ', $failed).' on '.$uri);
218
-            }
219
-            if ($throwExceptions) {
220
-                throw new NeedPrivileges($uri, $failed);
221
-            } else {
222
-                return false;
223
-            }
224
-        }
225
-
226
-        return true;
227
-    }
228
-
229
-    /**
230
-     * Returns the standard users' principal.
231
-     *
232
-     * This is one authoritative principal url for the current user.
233
-     * This method will return null if the user wasn't logged in.
234
-     *
235
-     * @return string|null
236
-     */
237
-    public function getCurrentUserPrincipal()
238
-    {
239
-        /** @var $authPlugin \Sabre\DAV\Auth\Plugin */
240
-        $authPlugin = $this->server->getPlugin('auth');
241
-        if (!$authPlugin) {
242
-            return null;
243
-        }
244
-
245
-        return $authPlugin->getCurrentPrincipal();
246
-    }
247
-
248
-    /**
249
-     * Returns a list of principals that's associated to the current
250
-     * user, either directly or through group membership.
251
-     *
252
-     * @return array
253
-     */
254
-    public function getCurrentUserPrincipals()
255
-    {
256
-        $currentUser = $this->getCurrentUserPrincipal();
257
-
258
-        if (is_null($currentUser)) {
259
-            return [];
260
-        }
261
-
262
-        return array_merge(
263
-            [$currentUser],
264
-            $this->getPrincipalMembership($currentUser)
265
-        );
266
-    }
267
-
268
-    /**
269
-     * Sets the default ACL rules.
270
-     *
271
-     * These rules are used for all nodes that don't implement the IACL interface.
272
-     */
273
-    public function setDefaultAcl(array $acl)
274
-    {
275
-        $this->defaultAcl = $acl;
276
-    }
277
-
278
-    /**
279
-     * Returns the default ACL rules.
280
-     *
281
-     * These rules are used for all nodes that don't implement the IACL interface.
282
-     *
283
-     * @return array
284
-     */
285
-    public function getDefaultAcl()
286
-    {
287
-        return $this->defaultAcl;
288
-    }
289
-
290
-    /**
291
-     * The default ACL rules.
292
-     *
293
-     * These rules are used for nodes that don't implement IACL. These default
294
-     * set of rules allow anyone to do anything, as long as they are
295
-     * authenticated.
296
-     *
297
-     * @var array
298
-     */
299
-    protected $defaultAcl = [
300
-        [
301
-            'principal' => '{DAV:}authenticated',
302
-            'protected' => true,
303
-            'privilege' => '{DAV:}all',
304
-        ],
305
-    ];
306
-
307
-    /**
308
-     * This array holds a cache for all the principals that are associated with
309
-     * a single principal.
310
-     *
311
-     * @var array
312
-     */
313
-    protected $principalMembershipCache = [];
314
-
315
-    /**
316
-     * Returns all the principal groups the specified principal is a member of.
317
-     *
318
-     * @param string $mainPrincipal
319
-     *
320
-     * @return array
321
-     */
322
-    public function getPrincipalMembership($mainPrincipal)
323
-    {
324
-        // First check our cache
325
-        if (isset($this->principalMembershipCache[$mainPrincipal])) {
326
-            return $this->principalMembershipCache[$mainPrincipal];
327
-        }
328
-
329
-        $check = [$mainPrincipal];
330
-        $principals = [];
331
-
332
-        while (count($check)) {
333
-            $principal = array_shift($check);
334
-
335
-            $node = $this->server->tree->getNodeForPath($principal);
336
-            if ($node instanceof IPrincipal) {
337
-                foreach ($node->getGroupMembership() as $groupMember) {
338
-                    if (!in_array($groupMember, $principals)) {
339
-                        $check[] = $groupMember;
340
-                        $principals[] = $groupMember;
341
-                    }
342
-                }
343
-            }
344
-        }
345
-
346
-        // Store the result in the cache
347
-        $this->principalMembershipCache[$mainPrincipal] = $principals;
348
-
349
-        return $principals;
350
-    }
351
-
352
-    /**
353
-     * Find out of a principal equals another principal.
354
-     *
355
-     * This is a quick way to find out whether a principal URI is part of a
356
-     * group, or any subgroups.
357
-     *
358
-     * The first argument is the principal URI you want to check against. For
359
-     * example the principal group, and the second argument is the principal of
360
-     * which you want to find out of it is the same as the first principal, or
361
-     * in a member of the first principal's group or subgroups.
362
-     *
363
-     * So the arguments are not interchangeable. If principal A is in group B,
364
-     * passing 'B', 'A' will yield true, but 'A', 'B' is false.
365
-     *
366
-     * If the second argument is not passed, we will use the current user
367
-     * principal.
368
-     *
369
-     * @param string $checkPrincipal
370
-     * @param string $currentPrincipal
371
-     *
372
-     * @return bool
373
-     */
374
-    public function principalMatchesPrincipal($checkPrincipal, $currentPrincipal = null)
375
-    {
376
-        if (is_null($currentPrincipal)) {
377
-            $currentPrincipal = $this->getCurrentUserPrincipal();
378
-        }
379
-        if ($currentPrincipal === $checkPrincipal) {
380
-            return true;
381
-        }
382
-        if (is_null($currentPrincipal)) {
383
-            return false;
384
-        }
385
-
386
-        return in_array(
387
-            $checkPrincipal,
388
-            $this->getPrincipalMembership($currentPrincipal)
389
-        );
390
-    }
391
-
392
-    /**
393
-     * Returns a tree of supported privileges for a resource.
394
-     *
395
-     * The returned array structure should be in this form:
396
-     *
397
-     * [
398
-     *    [
399
-     *       'privilege' => '{DAV:}read',
400
-     *       'abstract'  => false,
401
-     *       'aggregates' => []
402
-     *    ]
403
-     * ]
404
-     *
405
-     * Privileges can be nested using "aggregates". Doing so means that
406
-     * if you assign someone the aggregating privilege, all the
407
-     * sub-privileges will automatically be granted.
408
-     *
409
-     * Marking a privilege as abstract means that the privilege cannot be
410
-     * directly assigned, but must be assigned via the parent privilege.
411
-     *
412
-     * So a more complex version might look like this:
413
-     *
414
-     * [
415
-     *    [
416
-     *       'privilege' => '{DAV:}read',
417
-     *       'abstract'  => false,
418
-     *       'aggregates' => [
419
-     *          [
420
-     *              'privilege'  => '{DAV:}read-acl',
421
-     *              'abstract'   => false,
422
-     *              'aggregates' => [],
423
-     *          ]
424
-     *       ]
425
-     *    ]
426
-     * ]
427
-     *
428
-     * @param string|INode $node
429
-     *
430
-     * @return array
431
-     */
432
-    public function getSupportedPrivilegeSet($node)
433
-    {
434
-        if (is_string($node)) {
435
-            $node = $this->server->tree->getNodeForPath($node);
436
-        }
437
-
438
-        $supportedPrivileges = null;
439
-        if ($node instanceof IACL) {
440
-            $supportedPrivileges = $node->getSupportedPrivilegeSet();
441
-        }
442
-
443
-        if (is_null($supportedPrivileges)) {
444
-            // Default
445
-            $supportedPrivileges = [
446
-                '{DAV:}read' => [
447
-                    'abstract' => false,
448
-                    'aggregates' => [
449
-                        '{DAV:}read-acl' => [
450
-                            'abstract' => false,
451
-                            'aggregates' => [],
452
-                        ],
453
-                        '{DAV:}read-current-user-privilege-set' => [
454
-                            'abstract' => false,
455
-                            'aggregates' => [],
456
-                        ],
457
-                    ],
458
-                ],
459
-                '{DAV:}write' => [
460
-                    'abstract' => false,
461
-                    'aggregates' => [
462
-                        '{DAV:}write-properties' => [
463
-                            'abstract' => false,
464
-                            'aggregates' => [],
465
-                        ],
466
-                        '{DAV:}write-content' => [
467
-                            'abstract' => false,
468
-                            'aggregates' => [],
469
-                        ],
470
-                        '{DAV:}unlock' => [
471
-                            'abstract' => false,
472
-                            'aggregates' => [],
473
-                        ],
474
-                    ],
475
-                ],
476
-            ];
477
-            if ($node instanceof DAV\ICollection) {
478
-                $supportedPrivileges['{DAV:}write']['aggregates']['{DAV:}bind'] = [
479
-                    'abstract' => false,
480
-                    'aggregates' => [],
481
-                ];
482
-                $supportedPrivileges['{DAV:}write']['aggregates']['{DAV:}unbind'] = [
483
-                    'abstract' => false,
484
-                    'aggregates' => [],
485
-                ];
486
-            }
487
-            if ($node instanceof IACL) {
488
-                $supportedPrivileges['{DAV:}write']['aggregates']['{DAV:}write-acl'] = [
489
-                    'abstract' => false,
490
-                    'aggregates' => [],
491
-                ];
492
-            }
493
-        }
494
-
495
-        $this->server->emit(
496
-            'getSupportedPrivilegeSet',
497
-            [$node, &$supportedPrivileges]
498
-        );
499
-
500
-        return $supportedPrivileges;
501
-    }
502
-
503
-    /**
504
-     * Returns the supported privilege set as a flat list.
505
-     *
506
-     * This is much easier to parse.
507
-     *
508
-     * The returned list will be index by privilege name.
509
-     * The value is a struct containing the following properties:
510
-     *   - aggregates
511
-     *   - abstract
512
-     *   - concrete
513
-     *
514
-     * @param string|INode $node
515
-     *
516
-     * @return array
517
-     */
518
-    final public function getFlatPrivilegeSet($node)
519
-    {
520
-        $privs = [
521
-            'abstract' => false,
522
-            'aggregates' => $this->getSupportedPrivilegeSet($node),
523
-        ];
524
-
525
-        $fpsTraverse = null;
526
-        $fpsTraverse = function ($privName, $privInfo, $concrete, &$flat) use (&$fpsTraverse) {
527
-            $myPriv = [
528
-                'privilege' => $privName,
529
-                'abstract' => isset($privInfo['abstract']) && $privInfo['abstract'],
530
-                'aggregates' => [],
531
-                'concrete' => isset($privInfo['abstract']) && $privInfo['abstract'] ? $concrete : $privName,
532
-            ];
533
-
534
-            if (isset($privInfo['aggregates'])) {
535
-                foreach ($privInfo['aggregates'] as $subPrivName => $subPrivInfo) {
536
-                    $myPriv['aggregates'][] = $subPrivName;
537
-                }
538
-            }
539
-
540
-            $flat[$privName] = $myPriv;
541
-
542
-            if (isset($privInfo['aggregates'])) {
543
-                foreach ($privInfo['aggregates'] as $subPrivName => $subPrivInfo) {
544
-                    $fpsTraverse($subPrivName, $subPrivInfo, $myPriv['concrete'], $flat);
545
-                }
546
-            }
547
-        };
548
-
549
-        $flat = [];
550
-        $fpsTraverse('{DAV:}all', $privs, null, $flat);
551
-
552
-        return $flat;
553
-    }
554
-
555
-    /**
556
-     * Returns the full ACL list.
557
-     *
558
-     * Either a uri or a INode may be passed.
559
-     *
560
-     * null will be returned if the node doesn't support ACLs.
561
-     *
562
-     * @param string|DAV\INode $node
563
-     *
564
-     * @return array
565
-     */
566
-    public function getAcl($node)
567
-    {
568
-        if (is_string($node)) {
569
-            $node = $this->server->tree->getNodeForPath($node);
570
-        }
571
-        if (!$node instanceof IACL) {
572
-            return $this->getDefaultAcl();
573
-        }
574
-        $acl = $node->getACL();
575
-        foreach ($this->adminPrincipals as $adminPrincipal) {
576
-            $acl[] = [
577
-                'principal' => $adminPrincipal,
578
-                'privilege' => '{DAV:}all',
579
-                'protected' => true,
580
-            ];
581
-        }
582
-
583
-        return $acl;
584
-    }
585
-
586
-    /**
587
-     * Returns a list of privileges the current user has
588
-     * on a particular node.
589
-     *
590
-     * Either a uri or a DAV\INode may be passed.
591
-     *
592
-     * null will be returned if the node doesn't support ACLs.
593
-     *
594
-     * @param string|DAV\INode $node
595
-     *
596
-     * @return array
597
-     */
598
-    public function getCurrentUserPrivilegeSet($node)
599
-    {
600
-        if (is_string($node)) {
601
-            $node = $this->server->tree->getNodeForPath($node);
602
-        }
603
-
604
-        $acl = $this->getACL($node);
605
-
606
-        $collected = [];
607
-
608
-        $isAuthenticated = null !== $this->getCurrentUserPrincipal();
609
-
610
-        foreach ($acl as $ace) {
611
-            $principal = $ace['principal'];
612
-
613
-            switch ($principal) {
614
-                case '{DAV:}owner':
615
-                    $owner = $node->getOwner();
616
-                    if ($owner && $this->principalMatchesPrincipal($owner)) {
617
-                        $collected[] = $ace;
618
-                    }
619
-                    break;
620
-
621
-                // 'all' matches for every user
622
-                case '{DAV:}all':
623
-                    $collected[] = $ace;
624
-                    break;
625
-
626
-                case '{DAV:}authenticated':
627
-                    // Authenticated users only
628
-                    if ($isAuthenticated) {
629
-                        $collected[] = $ace;
630
-                    }
631
-                    break;
632
-
633
-                case '{DAV:}unauthenticated':
634
-                    // Unauthenticated users only
635
-                    if (!$isAuthenticated) {
636
-                        $collected[] = $ace;
637
-                    }
638
-                    break;
639
-
640
-                default:
641
-                    if ($this->principalMatchesPrincipal($ace['principal'])) {
642
-                        $collected[] = $ace;
643
-                    }
644
-                    break;
645
-            }
646
-        }
647
-
648
-        // Now we deduct all aggregated privileges.
649
-        $flat = $this->getFlatPrivilegeSet($node);
650
-
651
-        $collected2 = [];
652
-        while (count($collected)) {
653
-            $current = array_pop($collected);
654
-            $collected2[] = $current['privilege'];
655
-
656
-            if (!isset($flat[$current['privilege']])) {
657
-                // Ignoring privileges that are not in the supported-privileges list.
658
-                $this->server->getLogger()->debug('A node has the "'.$current['privilege'].'" in its ACL list, but this privilege was not reported in the supportedPrivilegeSet list. This will be ignored.');
659
-                continue;
660
-            }
661
-            foreach ($flat[$current['privilege']]['aggregates'] as $subPriv) {
662
-                $collected2[] = $subPriv;
663
-                $collected[] = $flat[$subPriv];
664
-            }
665
-        }
666
-
667
-        return array_values(array_unique($collected2));
668
-    }
669
-
670
-    /**
671
-     * Returns a principal based on its uri.
672
-     *
673
-     * Returns null if the principal could not be found.
674
-     *
675
-     * @param string $uri
676
-     *
677
-     * @return string|null
678
-     */
679
-    public function getPrincipalByUri($uri)
680
-    {
681
-        $result = null;
682
-        $collections = $this->principalCollectionSet;
683
-        foreach ($collections as $collection) {
684
-            try {
685
-                $principalCollection = $this->server->tree->getNodeForPath($collection);
686
-            } catch (NotFound $e) {
687
-                // Ignore and move on
688
-                continue;
689
-            }
690
-
691
-            if (!$principalCollection instanceof IPrincipalCollection) {
692
-                // Not a principal collection, we're simply going to ignore
693
-                // this.
694
-                continue;
695
-            }
696
-
697
-            $result = $principalCollection->findByUri($uri);
698
-            if ($result) {
699
-                return $result;
700
-            }
701
-        }
702
-    }
703
-
704
-    /**
705
-     * Principal property search.
706
-     *
707
-     * This method can search for principals matching certain values in
708
-     * properties.
709
-     *
710
-     * This method will return a list of properties for the matched properties.
711
-     *
712
-     * @param array  $searchProperties    The properties to search on. This is a
713
-     *                                    key-value list. The keys are property
714
-     *                                    names, and the values the strings to
715
-     *                                    match them on.
716
-     * @param array  $requestedProperties this is the list of properties to
717
-     *                                    return for every match
718
-     * @param string $collectionUri       the principal collection to search on.
719
-     *                                    If this is ommitted, the standard
720
-     *                                    principal collection-set will be used
721
-     * @param string $test                "allof" to use AND to search the
722
-     *                                    properties. 'anyof' for OR.
723
-     *
724
-     * @return array This method returns an array structure similar to
725
-     *               Sabre\DAV\Server::getPropertiesForPath. Returned
726
-     *               properties are index by a HTTP status code.
727
-     */
728
-    public function principalSearch(array $searchProperties, array $requestedProperties, $collectionUri = null, $test = 'allof')
729
-    {
730
-        if (!is_null($collectionUri)) {
731
-            $uris = [$collectionUri];
732
-        } else {
733
-            $uris = $this->principalCollectionSet;
734
-        }
735
-
736
-        $lookupResults = [];
737
-        foreach ($uris as $uri) {
738
-            $principalCollection = $this->server->tree->getNodeForPath($uri);
739
-            if (!$principalCollection instanceof IPrincipalCollection) {
740
-                // Not a principal collection, we're simply going to ignore
741
-                // this.
742
-                continue;
743
-            }
744
-
745
-            $results = $principalCollection->searchPrincipals($searchProperties, $test);
746
-            foreach ($results as $result) {
747
-                $lookupResults[] = rtrim($uri, '/').'/'.$result;
748
-            }
749
-        }
750
-
751
-        $matches = [];
752
-
753
-        foreach ($lookupResults as $lookupResult) {
754
-            list($matches[]) = $this->server->getPropertiesForPath($lookupResult, $requestedProperties, 0);
755
-        }
756
-
757
-        return $matches;
758
-    }
759
-
760
-    /**
761
-     * Sets up the plugin.
762
-     *
763
-     * This method is automatically called by the server class.
764
-     */
765
-    public function initialize(DAV\Server $server)
766
-    {
767
-        if ($this->allowUnauthenticatedAccess) {
768
-            $authPlugin = $server->getPlugin('auth');
769
-            if (!$authPlugin) {
770
-                throw new \Exception('The Auth plugin must be loaded before the ACL plugin if you want to allow unauthenticated access.');
771
-            }
772
-            $authPlugin->autoRequireLogin = false;
773
-        }
774
-
775
-        $this->server = $server;
776
-        $server->on('propFind', [$this, 'propFind'], 20);
777
-        $server->on('beforeMethod:*', [$this, 'beforeMethod'], 20);
778
-        $server->on('beforeBind', [$this, 'beforeBind'], 20);
779
-        $server->on('beforeUnbind', [$this, 'beforeUnbind'], 20);
780
-        $server->on('propPatch', [$this, 'propPatch']);
781
-        $server->on('beforeUnlock', [$this, 'beforeUnlock'], 20);
782
-        $server->on('report', [$this, 'report']);
783
-        $server->on('method:ACL', [$this, 'httpAcl']);
784
-        $server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']);
785
-        $server->on('getPrincipalByUri', function ($principal, &$uri) {
786
-            $uri = $this->getPrincipalByUri($principal);
787
-
788
-            // Break event chain
789
-            if ($uri) {
790
-                return false;
791
-            }
792
-        });
793
-
794
-        array_push($server->protectedProperties,
795
-            '{DAV:}alternate-URI-set',
796
-            '{DAV:}principal-URL',
797
-            '{DAV:}group-membership',
798
-            '{DAV:}principal-collection-set',
799
-            '{DAV:}current-user-principal',
800
-            '{DAV:}supported-privilege-set',
801
-            '{DAV:}current-user-privilege-set',
802
-            '{DAV:}acl',
803
-            '{DAV:}acl-restrictions',
804
-            '{DAV:}inherited-acl-set',
805
-            '{DAV:}owner',
806
-            '{DAV:}group'
807
-        );
808
-
809
-        // Automatically mapping nodes implementing IPrincipal to the
810
-        // {DAV:}principal resourcetype.
811
-        $server->resourceTypeMapping['Sabre\\DAVACL\\IPrincipal'] = '{DAV:}principal';
812
-
813
-        // Mapping the group-member-set property to the HrefList property
814
-        // class.
815
-        $server->xml->elementMap['{DAV:}group-member-set'] = 'Sabre\\DAV\\Xml\\Property\\Href';
816
-        $server->xml->elementMap['{DAV:}acl'] = 'Sabre\\DAVACL\\Xml\\Property\\Acl';
817
-        $server->xml->elementMap['{DAV:}acl-principal-prop-set'] = 'Sabre\\DAVACL\\Xml\\Request\\AclPrincipalPropSetReport';
818
-        $server->xml->elementMap['{DAV:}expand-property'] = 'Sabre\\DAVACL\\Xml\\Request\\ExpandPropertyReport';
819
-        $server->xml->elementMap['{DAV:}principal-property-search'] = 'Sabre\\DAVACL\\Xml\\Request\\PrincipalPropertySearchReport';
820
-        $server->xml->elementMap['{DAV:}principal-search-property-set'] = 'Sabre\\DAVACL\\Xml\\Request\\PrincipalSearchPropertySetReport';
821
-        $server->xml->elementMap['{DAV:}principal-match'] = 'Sabre\\DAVACL\\Xml\\Request\\PrincipalMatchReport';
822
-    }
823
-
824
-    /* {{{ Event handlers */
825
-
826
-    /**
827
-     * Triggered before any method is handled.
828
-     */
829
-    public function beforeMethod(RequestInterface $request, ResponseInterface $response)
830
-    {
831
-        $method = $request->getMethod();
832
-        $path = $request->getPath();
833
-
834
-        $exists = $this->server->tree->nodeExists($path);
835
-
836
-        // If the node doesn't exists, none of these checks apply
837
-        if (!$exists) {
838
-            return;
839
-        }
840
-
841
-        switch ($method) {
842
-            case 'GET':
843
-            case 'HEAD':
844
-            case 'OPTIONS':
845
-                // For these 3 we only need to know if the node is readable.
846
-                $this->checkPrivileges($path, '{DAV:}read');
847
-                break;
848
-
849
-            case 'PUT':
850
-            case 'LOCK':
851
-                // This method requires the write-content priv if the node
852
-                // already exists, and bind on the parent if the node is being
853
-                // created.
854
-                // The bind privilege is handled in the beforeBind event.
855
-                $this->checkPrivileges($path, '{DAV:}write-content');
856
-                break;
857
-
858
-            case 'UNLOCK':
859
-                // Unlock is always allowed at the moment.
860
-                break;
861
-
862
-            case 'PROPPATCH':
863
-                $this->checkPrivileges($path, '{DAV:}write-properties');
864
-                break;
865
-
866
-            case 'ACL':
867
-                $this->checkPrivileges($path, '{DAV:}write-acl');
868
-                break;
869
-
870
-            case 'COPY':
871
-            case 'MOVE':
872
-                // Copy requires read privileges on the entire source tree.
873
-                // If the target exists write-content normally needs to be
874
-                // checked, however, we're deleting the node beforehand and
875
-                // creating a new one after, so this is handled by the
876
-                // beforeUnbind event.
877
-                //
878
-                // The creation of the new node is handled by the beforeBind
879
-                // event.
880
-                //
881
-                // If MOVE is used beforeUnbind will also be used to check if
882
-                // the sourcenode can be deleted.
883
-                $this->checkPrivileges($path, '{DAV:}read', self::R_RECURSIVE);
884
-                break;
885
-        }
886
-    }
887
-
888
-    /**
889
-     * Triggered before a new node is created.
890
-     *
891
-     * This allows us to check permissions for any operation that creates a
892
-     * new node, such as PUT, MKCOL, MKCALENDAR, LOCK, COPY and MOVE.
893
-     *
894
-     * @param string $uri
895
-     */
896
-    public function beforeBind($uri)
897
-    {
898
-        list($parentUri) = Uri\split($uri);
899
-        $this->checkPrivileges($parentUri, '{DAV:}bind');
900
-    }
901
-
902
-    /**
903
-     * Triggered before a node is deleted.
904
-     *
905
-     * This allows us to check permissions for any operation that will delete
906
-     * an existing node.
907
-     *
908
-     * @param string $uri
909
-     */
910
-    public function beforeUnbind($uri)
911
-    {
912
-        list($parentUri) = Uri\split($uri);
913
-        $this->checkPrivileges($parentUri, '{DAV:}unbind', self::R_RECURSIVEPARENTS);
914
-    }
915
-
916
-    /**
917
-     * Triggered before a node is unlocked.
918
-     *
919
-     * @param string $uri
920
-     * @TODO: not yet implemented
921
-     */
922
-    public function beforeUnlock($uri, DAV\Locks\LockInfo $lock)
923
-    {
924
-    }
925
-
926
-    /**
927
-     * Triggered before properties are looked up in specific nodes.
928
-     *
929
-     * @TODO really should be broken into multiple methods, or even a class.
930
-     */
931
-    public function propFind(DAV\PropFind $propFind, DAV\INode $node)
932
-    {
933
-        $path = $propFind->getPath();
934
-
935
-        // Checking the read permission
936
-        if (!$this->checkPrivileges($path, '{DAV:}read', self::R_PARENT, false)) {
937
-            // User is not allowed to read properties
938
-
939
-            // Returning false causes the property-fetching system to pretend
940
-            // that the node does not exist, and will cause it to be hidden
941
-            // from listings such as PROPFIND or the browser plugin.
942
-            if ($this->hideNodesFromListings) {
943
-                return false;
944
-            }
945
-
946
-            // Otherwise we simply mark every property as 403.
947
-            foreach ($propFind->getRequestedProperties() as $requestedProperty) {
948
-                $propFind->set($requestedProperty, null, 403);
949
-            }
950
-
951
-            return;
952
-        }
953
-
954
-        /* Adding principal properties */
955
-        if ($node instanceof IPrincipal) {
956
-            $propFind->handle('{DAV:}alternate-URI-set', function () use ($node) {
957
-                return new Href($node->getAlternateUriSet());
958
-            });
959
-            $propFind->handle('{DAV:}principal-URL', function () use ($node) {
960
-                return new Href($node->getPrincipalUrl().'/');
961
-            });
962
-            $propFind->handle('{DAV:}group-member-set', function () use ($node) {
963
-                $members = $node->getGroupMemberSet();
964
-                foreach ($members as $k => $member) {
965
-                    $members[$k] = rtrim($member, '/').'/';
966
-                }
967
-
968
-                return new Href($members);
969
-            });
970
-            $propFind->handle('{DAV:}group-membership', function () use ($node) {
971
-                $members = $node->getGroupMembership();
972
-                foreach ($members as $k => $member) {
973
-                    $members[$k] = rtrim($member, '/').'/';
974
-                }
975
-
976
-                return new Href($members);
977
-            });
978
-            $propFind->handle('{DAV:}displayname', [$node, 'getDisplayName']);
979
-        }
980
-
981
-        $propFind->handle('{DAV:}principal-collection-set', function () {
982
-            $val = $this->principalCollectionSet;
983
-            // Ensuring all collections end with a slash
984
-            foreach ($val as $k => $v) {
985
-                $val[$k] = $v.'/';
986
-            }
987
-
988
-            return new Href($val);
989
-        });
990
-        $propFind->handle('{DAV:}current-user-principal', function () {
991
-            if ($url = $this->getCurrentUserPrincipal()) {
992
-                return new Xml\Property\Principal(Xml\Property\Principal::HREF, $url.'/');
993
-            } else {
994
-                return new Xml\Property\Principal(Xml\Property\Principal::UNAUTHENTICATED);
995
-            }
996
-        });
997
-        $propFind->handle('{DAV:}supported-privilege-set', function () use ($node) {
998
-            return new Xml\Property\SupportedPrivilegeSet($this->getSupportedPrivilegeSet($node));
999
-        });
1000
-        $propFind->handle('{DAV:}current-user-privilege-set', function () use ($node, $propFind, $path) {
1001
-            if (!$this->checkPrivileges($path, '{DAV:}read-current-user-privilege-set', self::R_PARENT, false)) {
1002
-                $propFind->set('{DAV:}current-user-privilege-set', null, 403);
1003
-            } else {
1004
-                $val = $this->getCurrentUserPrivilegeSet($node);
1005
-
1006
-                return new Xml\Property\CurrentUserPrivilegeSet($val);
1007
-            }
1008
-        });
1009
-        $propFind->handle('{DAV:}acl', function () use ($node, $propFind, $path) {
1010
-            /* The ACL property contains all the permissions */
1011
-            if (!$this->checkPrivileges($path, '{DAV:}read-acl', self::R_PARENT, false)) {
1012
-                $propFind->set('{DAV:}acl', null, 403);
1013
-            } else {
1014
-                $acl = $this->getACL($node);
1015
-
1016
-                return new Xml\Property\Acl($this->getACL($node));
1017
-            }
1018
-        });
1019
-        $propFind->handle('{DAV:}acl-restrictions', function () {
1020
-            return new Xml\Property\AclRestrictions();
1021
-        });
1022
-
1023
-        /* Adding ACL properties */
1024
-        if ($node instanceof IACL) {
1025
-            $propFind->handle('{DAV:}owner', function () use ($node) {
1026
-                return new Href($node->getOwner().'/');
1027
-            });
1028
-        }
1029
-    }
1030
-
1031
-    /**
1032
-     * This method intercepts PROPPATCH methods and make sure the
1033
-     * group-member-set is updated correctly.
1034
-     *
1035
-     * @param string $path
1036
-     */
1037
-    public function propPatch($path, DAV\PropPatch $propPatch)
1038
-    {
1039
-        $propPatch->handle('{DAV:}group-member-set', function ($value) use ($path) {
1040
-            if (is_null($value)) {
1041
-                $memberSet = [];
1042
-            } elseif ($value instanceof Href) {
1043
-                $memberSet = array_map(
1044
-                    [$this->server, 'calculateUri'],
1045
-                    $value->getHrefs()
1046
-                );
1047
-            } else {
1048
-                throw new DAV\Exception('The group-member-set property MUST be an instance of Sabre\DAV\Property\HrefList or null');
1049
-            }
1050
-            $node = $this->server->tree->getNodeForPath($path);
1051
-            if (!($node instanceof IPrincipal)) {
1052
-                // Fail
1053
-                return false;
1054
-            }
1055
-
1056
-            $node->setGroupMemberSet($memberSet);
1057
-            // We must also clear our cache, just in case
1058
-
1059
-            $this->principalMembershipCache = [];
1060
-
1061
-            return true;
1062
-        });
1063
-    }
1064
-
1065
-    /**
1066
-     * This method handles HTTP REPORT requests.
1067
-     *
1068
-     * @param string $reportName
1069
-     * @param mixed  $report
1070
-     * @param mixed  $path
1071
-     */
1072
-    public function report($reportName, $report, $path)
1073
-    {
1074
-        switch ($reportName) {
1075
-            case '{DAV:}principal-property-search':
1076
-                $this->server->transactionType = 'report-principal-property-search';
1077
-                $this->principalPropertySearchReport($path, $report);
1078
-
1079
-                return false;
1080
-            case '{DAV:}principal-search-property-set':
1081
-                $this->server->transactionType = 'report-principal-search-property-set';
1082
-                $this->principalSearchPropertySetReport($path, $report);
1083
-
1084
-                return false;
1085
-            case '{DAV:}expand-property':
1086
-                $this->server->transactionType = 'report-expand-property';
1087
-                $this->expandPropertyReport($path, $report);
1088
-
1089
-                return false;
1090
-            case '{DAV:}principal-match':
1091
-                $this->server->transactionType = 'report-principal-match';
1092
-                $this->principalMatchReport($path, $report);
1093
-
1094
-                return false;
1095
-            case '{DAV:}acl-principal-prop-set':
1096
-                $this->server->transactionType = 'acl-principal-prop-set';
1097
-                $this->aclPrincipalPropSetReport($path, $report);
1098
-
1099
-                return false;
1100
-        }
1101
-    }
1102
-
1103
-    /**
1104
-     * This method is responsible for handling the 'ACL' event.
1105
-     *
1106
-     * @return bool
1107
-     */
1108
-    public function httpAcl(RequestInterface $request, ResponseInterface $response)
1109
-    {
1110
-        $path = $request->getPath();
1111
-        $body = $request->getBodyAsString();
1112
-
1113
-        if (!$body) {
1114
-            throw new DAV\Exception\BadRequest('XML body expected in ACL request');
1115
-        }
1116
-
1117
-        $acl = $this->server->xml->expect('{DAV:}acl', $body);
1118
-        $newAcl = $acl->getPrivileges();
1119
-
1120
-        // Normalizing urls
1121
-        foreach ($newAcl as $k => $newAce) {
1122
-            $newAcl[$k]['principal'] = $this->server->calculateUri($newAce['principal']);
1123
-        }
1124
-        $node = $this->server->tree->getNodeForPath($path);
1125
-
1126
-        if (!$node instanceof IACL) {
1127
-            throw new DAV\Exception\MethodNotAllowed('This node does not support the ACL method');
1128
-        }
1129
-
1130
-        $oldAcl = $this->getACL($node);
1131
-
1132
-        $supportedPrivileges = $this->getFlatPrivilegeSet($node);
1133
-
1134
-        /* Checking if protected principals from the existing principal set are
35
+	/**
36
+	 * Recursion constants.
37
+	 *
38
+	 * This only checks the base node
39
+	 */
40
+	const R_PARENT = 1;
41
+
42
+	/**
43
+	 * Recursion constants.
44
+	 *
45
+	 * This checks every node in the tree
46
+	 */
47
+	const R_RECURSIVE = 2;
48
+
49
+	/**
50
+	 * Recursion constants.
51
+	 *
52
+	 * This checks every parentnode in the tree, but not leaf-nodes.
53
+	 */
54
+	const R_RECURSIVEPARENTS = 3;
55
+
56
+	/**
57
+	 * Reference to server object.
58
+	 *
59
+	 * @var DAV\Server
60
+	 */
61
+	protected $server;
62
+
63
+	/**
64
+	 * List of urls containing principal collections.
65
+	 * Modify this if your principals are located elsewhere.
66
+	 *
67
+	 * @var array
68
+	 */
69
+	public $principalCollectionSet = [
70
+		'principals',
71
+	];
72
+
73
+	/**
74
+	 * By default nodes that are inaccessible by the user, can still be seen
75
+	 * in directory listings (PROPFIND on parent with Depth: 1).
76
+	 *
77
+	 * In certain cases it's desirable to hide inaccessible nodes. Setting this
78
+	 * to true will cause these nodes to be hidden from directory listings.
79
+	 *
80
+	 * @var bool
81
+	 */
82
+	public $hideNodesFromListings = false;
83
+
84
+	/**
85
+	 * This list of properties are the properties a client can search on using
86
+	 * the {DAV:}principal-property-search report.
87
+	 *
88
+	 * The keys are the property names, values are descriptions.
89
+	 *
90
+	 * @var array
91
+	 */
92
+	public $principalSearchPropertySet = [
93
+		'{DAV:}displayname' => 'Display name',
94
+		'{http://sabredav.org/ns}email-address' => 'Email address',
95
+	];
96
+
97
+	/**
98
+	 * Any principal uri's added here, will automatically be added to the list
99
+	 * of ACL's. They will effectively receive {DAV:}all privileges, as a
100
+	 * protected privilege.
101
+	 *
102
+	 * @var array
103
+	 */
104
+	public $adminPrincipals = [];
105
+
106
+	/**
107
+	 * The ACL plugin allows privileges to be assigned to users that are not
108
+	 * logged in. To facilitate that, it modifies the auth plugin's behavior
109
+	 * to only require login when a privileged operation was denied.
110
+	 *
111
+	 * Unauthenticated access can be considered a security concern, so it's
112
+	 * possible to turn this feature off to harden the server's security.
113
+	 *
114
+	 * @var bool
115
+	 */
116
+	public $allowUnauthenticatedAccess = true;
117
+
118
+	/**
119
+	 * Returns a list of features added by this plugin.
120
+	 *
121
+	 * This list is used in the response of a HTTP OPTIONS request.
122
+	 *
123
+	 * @return array
124
+	 */
125
+	public function getFeatures()
126
+	{
127
+		return ['access-control', 'calendarserver-principal-property-search'];
128
+	}
129
+
130
+	/**
131
+	 * Returns a list of available methods for a given url.
132
+	 *
133
+	 * @param string $uri
134
+	 *
135
+	 * @return array
136
+	 */
137
+	public function getMethods($uri)
138
+	{
139
+		return ['ACL'];
140
+	}
141
+
142
+	/**
143
+	 * Returns a plugin name.
144
+	 *
145
+	 * Using this name other plugins will be able to access other plugins
146
+	 * using Sabre\DAV\Server::getPlugin
147
+	 *
148
+	 * @return string
149
+	 */
150
+	public function getPluginName()
151
+	{
152
+		return 'acl';
153
+	}
154
+
155
+	/**
156
+	 * Returns a list of reports this plugin supports.
157
+	 *
158
+	 * This will be used in the {DAV:}supported-report-set property.
159
+	 * Note that you still need to subscribe to the 'report' event to actually
160
+	 * implement them
161
+	 *
162
+	 * @param string $uri
163
+	 *
164
+	 * @return array
165
+	 */
166
+	public function getSupportedReportSet($uri)
167
+	{
168
+		return [
169
+			'{DAV:}expand-property',
170
+			'{DAV:}principal-match',
171
+			'{DAV:}principal-property-search',
172
+			'{DAV:}principal-search-property-set',
173
+		];
174
+	}
175
+
176
+	/**
177
+	 * Checks if the current user has the specified privilege(s).
178
+	 *
179
+	 * You can specify a single privilege, or a list of privileges.
180
+	 * This method will throw an exception if the privilege is not available
181
+	 * and return true otherwise.
182
+	 *
183
+	 * @param string       $uri
184
+	 * @param array|string $privileges
185
+	 * @param int          $recursion
186
+	 * @param bool         $throwExceptions if set to false, this method won't throw exceptions
187
+	 *
188
+	 * @throws NeedPrivileges
189
+	 * @throws NotAuthenticated
190
+	 *
191
+	 * @return bool
192
+	 */
193
+	public function checkPrivileges($uri, $privileges, $recursion = self::R_PARENT, $throwExceptions = true)
194
+	{
195
+		if (!is_array($privileges)) {
196
+			$privileges = [$privileges];
197
+		}
198
+
199
+		$acl = $this->getCurrentUserPrivilegeSet($uri);
200
+
201
+		$failed = [];
202
+		foreach ($privileges as $priv) {
203
+			if (!in_array($priv, $acl)) {
204
+				$failed[] = $priv;
205
+			}
206
+		}
207
+
208
+		if ($failed) {
209
+			if ($this->allowUnauthenticatedAccess && is_null($this->getCurrentUserPrincipal())) {
210
+				// We are not authenticated. Kicking in the Auth plugin.
211
+				$authPlugin = $this->server->getPlugin('auth');
212
+				$reasons = $authPlugin->getLoginFailedReasons();
213
+				$authPlugin->challenge(
214
+					$this->server->httpRequest,
215
+					$this->server->httpResponse
216
+				);
217
+				throw new NotAuthenticated(implode(', ', $reasons).'. Login was needed for privilege: '.implode(', ', $failed).' on '.$uri);
218
+			}
219
+			if ($throwExceptions) {
220
+				throw new NeedPrivileges($uri, $failed);
221
+			} else {
222
+				return false;
223
+			}
224
+		}
225
+
226
+		return true;
227
+	}
228
+
229
+	/**
230
+	 * Returns the standard users' principal.
231
+	 *
232
+	 * This is one authoritative principal url for the current user.
233
+	 * This method will return null if the user wasn't logged in.
234
+	 *
235
+	 * @return string|null
236
+	 */
237
+	public function getCurrentUserPrincipal()
238
+	{
239
+		/** @var $authPlugin \Sabre\DAV\Auth\Plugin */
240
+		$authPlugin = $this->server->getPlugin('auth');
241
+		if (!$authPlugin) {
242
+			return null;
243
+		}
244
+
245
+		return $authPlugin->getCurrentPrincipal();
246
+	}
247
+
248
+	/**
249
+	 * Returns a list of principals that's associated to the current
250
+	 * user, either directly or through group membership.
251
+	 *
252
+	 * @return array
253
+	 */
254
+	public function getCurrentUserPrincipals()
255
+	{
256
+		$currentUser = $this->getCurrentUserPrincipal();
257
+
258
+		if (is_null($currentUser)) {
259
+			return [];
260
+		}
261
+
262
+		return array_merge(
263
+			[$currentUser],
264
+			$this->getPrincipalMembership($currentUser)
265
+		);
266
+	}
267
+
268
+	/**
269
+	 * Sets the default ACL rules.
270
+	 *
271
+	 * These rules are used for all nodes that don't implement the IACL interface.
272
+	 */
273
+	public function setDefaultAcl(array $acl)
274
+	{
275
+		$this->defaultAcl = $acl;
276
+	}
277
+
278
+	/**
279
+	 * Returns the default ACL rules.
280
+	 *
281
+	 * These rules are used for all nodes that don't implement the IACL interface.
282
+	 *
283
+	 * @return array
284
+	 */
285
+	public function getDefaultAcl()
286
+	{
287
+		return $this->defaultAcl;
288
+	}
289
+
290
+	/**
291
+	 * The default ACL rules.
292
+	 *
293
+	 * These rules are used for nodes that don't implement IACL. These default
294
+	 * set of rules allow anyone to do anything, as long as they are
295
+	 * authenticated.
296
+	 *
297
+	 * @var array
298
+	 */
299
+	protected $defaultAcl = [
300
+		[
301
+			'principal' => '{DAV:}authenticated',
302
+			'protected' => true,
303
+			'privilege' => '{DAV:}all',
304
+		],
305
+	];
306
+
307
+	/**
308
+	 * This array holds a cache for all the principals that are associated with
309
+	 * a single principal.
310
+	 *
311
+	 * @var array
312
+	 */
313
+	protected $principalMembershipCache = [];
314
+
315
+	/**
316
+	 * Returns all the principal groups the specified principal is a member of.
317
+	 *
318
+	 * @param string $mainPrincipal
319
+	 *
320
+	 * @return array
321
+	 */
322
+	public function getPrincipalMembership($mainPrincipal)
323
+	{
324
+		// First check our cache
325
+		if (isset($this->principalMembershipCache[$mainPrincipal])) {
326
+			return $this->principalMembershipCache[$mainPrincipal];
327
+		}
328
+
329
+		$check = [$mainPrincipal];
330
+		$principals = [];
331
+
332
+		while (count($check)) {
333
+			$principal = array_shift($check);
334
+
335
+			$node = $this->server->tree->getNodeForPath($principal);
336
+			if ($node instanceof IPrincipal) {
337
+				foreach ($node->getGroupMembership() as $groupMember) {
338
+					if (!in_array($groupMember, $principals)) {
339
+						$check[] = $groupMember;
340
+						$principals[] = $groupMember;
341
+					}
342
+				}
343
+			}
344
+		}
345
+
346
+		// Store the result in the cache
347
+		$this->principalMembershipCache[$mainPrincipal] = $principals;
348
+
349
+		return $principals;
350
+	}
351
+
352
+	/**
353
+	 * Find out of a principal equals another principal.
354
+	 *
355
+	 * This is a quick way to find out whether a principal URI is part of a
356
+	 * group, or any subgroups.
357
+	 *
358
+	 * The first argument is the principal URI you want to check against. For
359
+	 * example the principal group, and the second argument is the principal of
360
+	 * which you want to find out of it is the same as the first principal, or
361
+	 * in a member of the first principal's group or subgroups.
362
+	 *
363
+	 * So the arguments are not interchangeable. If principal A is in group B,
364
+	 * passing 'B', 'A' will yield true, but 'A', 'B' is false.
365
+	 *
366
+	 * If the second argument is not passed, we will use the current user
367
+	 * principal.
368
+	 *
369
+	 * @param string $checkPrincipal
370
+	 * @param string $currentPrincipal
371
+	 *
372
+	 * @return bool
373
+	 */
374
+	public function principalMatchesPrincipal($checkPrincipal, $currentPrincipal = null)
375
+	{
376
+		if (is_null($currentPrincipal)) {
377
+			$currentPrincipal = $this->getCurrentUserPrincipal();
378
+		}
379
+		if ($currentPrincipal === $checkPrincipal) {
380
+			return true;
381
+		}
382
+		if (is_null($currentPrincipal)) {
383
+			return false;
384
+		}
385
+
386
+		return in_array(
387
+			$checkPrincipal,
388
+			$this->getPrincipalMembership($currentPrincipal)
389
+		);
390
+	}
391
+
392
+	/**
393
+	 * Returns a tree of supported privileges for a resource.
394
+	 *
395
+	 * The returned array structure should be in this form:
396
+	 *
397
+	 * [
398
+	 *    [
399
+	 *       'privilege' => '{DAV:}read',
400
+	 *       'abstract'  => false,
401
+	 *       'aggregates' => []
402
+	 *    ]
403
+	 * ]
404
+	 *
405
+	 * Privileges can be nested using "aggregates". Doing so means that
406
+	 * if you assign someone the aggregating privilege, all the
407
+	 * sub-privileges will automatically be granted.
408
+	 *
409
+	 * Marking a privilege as abstract means that the privilege cannot be
410
+	 * directly assigned, but must be assigned via the parent privilege.
411
+	 *
412
+	 * So a more complex version might look like this:
413
+	 *
414
+	 * [
415
+	 *    [
416
+	 *       'privilege' => '{DAV:}read',
417
+	 *       'abstract'  => false,
418
+	 *       'aggregates' => [
419
+	 *          [
420
+	 *              'privilege'  => '{DAV:}read-acl',
421
+	 *              'abstract'   => false,
422
+	 *              'aggregates' => [],
423
+	 *          ]
424
+	 *       ]
425
+	 *    ]
426
+	 * ]
427
+	 *
428
+	 * @param string|INode $node
429
+	 *
430
+	 * @return array
431
+	 */
432
+	public function getSupportedPrivilegeSet($node)
433
+	{
434
+		if (is_string($node)) {
435
+			$node = $this->server->tree->getNodeForPath($node);
436
+		}
437
+
438
+		$supportedPrivileges = null;
439
+		if ($node instanceof IACL) {
440
+			$supportedPrivileges = $node->getSupportedPrivilegeSet();
441
+		}
442
+
443
+		if (is_null($supportedPrivileges)) {
444
+			// Default
445
+			$supportedPrivileges = [
446
+				'{DAV:}read' => [
447
+					'abstract' => false,
448
+					'aggregates' => [
449
+						'{DAV:}read-acl' => [
450
+							'abstract' => false,
451
+							'aggregates' => [],
452
+						],
453
+						'{DAV:}read-current-user-privilege-set' => [
454
+							'abstract' => false,
455
+							'aggregates' => [],
456
+						],
457
+					],
458
+				],
459
+				'{DAV:}write' => [
460
+					'abstract' => false,
461
+					'aggregates' => [
462
+						'{DAV:}write-properties' => [
463
+							'abstract' => false,
464
+							'aggregates' => [],
465
+						],
466
+						'{DAV:}write-content' => [
467
+							'abstract' => false,
468
+							'aggregates' => [],
469
+						],
470
+						'{DAV:}unlock' => [
471
+							'abstract' => false,
472
+							'aggregates' => [],
473
+						],
474
+					],
475
+				],
476
+			];
477
+			if ($node instanceof DAV\ICollection) {
478
+				$supportedPrivileges['{DAV:}write']['aggregates']['{DAV:}bind'] = [
479
+					'abstract' => false,
480
+					'aggregates' => [],
481
+				];
482
+				$supportedPrivileges['{DAV:}write']['aggregates']['{DAV:}unbind'] = [
483
+					'abstract' => false,
484
+					'aggregates' => [],
485
+				];
486
+			}
487
+			if ($node instanceof IACL) {
488
+				$supportedPrivileges['{DAV:}write']['aggregates']['{DAV:}write-acl'] = [
489
+					'abstract' => false,
490
+					'aggregates' => [],
491
+				];
492
+			}
493
+		}
494
+
495
+		$this->server->emit(
496
+			'getSupportedPrivilegeSet',
497
+			[$node, &$supportedPrivileges]
498
+		);
499
+
500
+		return $supportedPrivileges;
501
+	}
502
+
503
+	/**
504
+	 * Returns the supported privilege set as a flat list.
505
+	 *
506
+	 * This is much easier to parse.
507
+	 *
508
+	 * The returned list will be index by privilege name.
509
+	 * The value is a struct containing the following properties:
510
+	 *   - aggregates
511
+	 *   - abstract
512
+	 *   - concrete
513
+	 *
514
+	 * @param string|INode $node
515
+	 *
516
+	 * @return array
517
+	 */
518
+	final public function getFlatPrivilegeSet($node)
519
+	{
520
+		$privs = [
521
+			'abstract' => false,
522
+			'aggregates' => $this->getSupportedPrivilegeSet($node),
523
+		];
524
+
525
+		$fpsTraverse = null;
526
+		$fpsTraverse = function ($privName, $privInfo, $concrete, &$flat) use (&$fpsTraverse) {
527
+			$myPriv = [
528
+				'privilege' => $privName,
529
+				'abstract' => isset($privInfo['abstract']) && $privInfo['abstract'],
530
+				'aggregates' => [],
531
+				'concrete' => isset($privInfo['abstract']) && $privInfo['abstract'] ? $concrete : $privName,
532
+			];
533
+
534
+			if (isset($privInfo['aggregates'])) {
535
+				foreach ($privInfo['aggregates'] as $subPrivName => $subPrivInfo) {
536
+					$myPriv['aggregates'][] = $subPrivName;
537
+				}
538
+			}
539
+
540
+			$flat[$privName] = $myPriv;
541
+
542
+			if (isset($privInfo['aggregates'])) {
543
+				foreach ($privInfo['aggregates'] as $subPrivName => $subPrivInfo) {
544
+					$fpsTraverse($subPrivName, $subPrivInfo, $myPriv['concrete'], $flat);
545
+				}
546
+			}
547
+		};
548
+
549
+		$flat = [];
550
+		$fpsTraverse('{DAV:}all', $privs, null, $flat);
551
+
552
+		return $flat;
553
+	}
554
+
555
+	/**
556
+	 * Returns the full ACL list.
557
+	 *
558
+	 * Either a uri or a INode may be passed.
559
+	 *
560
+	 * null will be returned if the node doesn't support ACLs.
561
+	 *
562
+	 * @param string|DAV\INode $node
563
+	 *
564
+	 * @return array
565
+	 */
566
+	public function getAcl($node)
567
+	{
568
+		if (is_string($node)) {
569
+			$node = $this->server->tree->getNodeForPath($node);
570
+		}
571
+		if (!$node instanceof IACL) {
572
+			return $this->getDefaultAcl();
573
+		}
574
+		$acl = $node->getACL();
575
+		foreach ($this->adminPrincipals as $adminPrincipal) {
576
+			$acl[] = [
577
+				'principal' => $adminPrincipal,
578
+				'privilege' => '{DAV:}all',
579
+				'protected' => true,
580
+			];
581
+		}
582
+
583
+		return $acl;
584
+	}
585
+
586
+	/**
587
+	 * Returns a list of privileges the current user has
588
+	 * on a particular node.
589
+	 *
590
+	 * Either a uri or a DAV\INode may be passed.
591
+	 *
592
+	 * null will be returned if the node doesn't support ACLs.
593
+	 *
594
+	 * @param string|DAV\INode $node
595
+	 *
596
+	 * @return array
597
+	 */
598
+	public function getCurrentUserPrivilegeSet($node)
599
+	{
600
+		if (is_string($node)) {
601
+			$node = $this->server->tree->getNodeForPath($node);
602
+		}
603
+
604
+		$acl = $this->getACL($node);
605
+
606
+		$collected = [];
607
+
608
+		$isAuthenticated = null !== $this->getCurrentUserPrincipal();
609
+
610
+		foreach ($acl as $ace) {
611
+			$principal = $ace['principal'];
612
+
613
+			switch ($principal) {
614
+				case '{DAV:}owner':
615
+					$owner = $node->getOwner();
616
+					if ($owner && $this->principalMatchesPrincipal($owner)) {
617
+						$collected[] = $ace;
618
+					}
619
+					break;
620
+
621
+				// 'all' matches for every user
622
+				case '{DAV:}all':
623
+					$collected[] = $ace;
624
+					break;
625
+
626
+				case '{DAV:}authenticated':
627
+					// Authenticated users only
628
+					if ($isAuthenticated) {
629
+						$collected[] = $ace;
630
+					}
631
+					break;
632
+
633
+				case '{DAV:}unauthenticated':
634
+					// Unauthenticated users only
635
+					if (!$isAuthenticated) {
636
+						$collected[] = $ace;
637
+					}
638
+					break;
639
+
640
+				default:
641
+					if ($this->principalMatchesPrincipal($ace['principal'])) {
642
+						$collected[] = $ace;
643
+					}
644
+					break;
645
+			}
646
+		}
647
+
648
+		// Now we deduct all aggregated privileges.
649
+		$flat = $this->getFlatPrivilegeSet($node);
650
+
651
+		$collected2 = [];
652
+		while (count($collected)) {
653
+			$current = array_pop($collected);
654
+			$collected2[] = $current['privilege'];
655
+
656
+			if (!isset($flat[$current['privilege']])) {
657
+				// Ignoring privileges that are not in the supported-privileges list.
658
+				$this->server->getLogger()->debug('A node has the "'.$current['privilege'].'" in its ACL list, but this privilege was not reported in the supportedPrivilegeSet list. This will be ignored.');
659
+				continue;
660
+			}
661
+			foreach ($flat[$current['privilege']]['aggregates'] as $subPriv) {
662
+				$collected2[] = $subPriv;
663
+				$collected[] = $flat[$subPriv];
664
+			}
665
+		}
666
+
667
+		return array_values(array_unique($collected2));
668
+	}
669
+
670
+	/**
671
+	 * Returns a principal based on its uri.
672
+	 *
673
+	 * Returns null if the principal could not be found.
674
+	 *
675
+	 * @param string $uri
676
+	 *
677
+	 * @return string|null
678
+	 */
679
+	public function getPrincipalByUri($uri)
680
+	{
681
+		$result = null;
682
+		$collections = $this->principalCollectionSet;
683
+		foreach ($collections as $collection) {
684
+			try {
685
+				$principalCollection = $this->server->tree->getNodeForPath($collection);
686
+			} catch (NotFound $e) {
687
+				// Ignore and move on
688
+				continue;
689
+			}
690
+
691
+			if (!$principalCollection instanceof IPrincipalCollection) {
692
+				// Not a principal collection, we're simply going to ignore
693
+				// this.
694
+				continue;
695
+			}
696
+
697
+			$result = $principalCollection->findByUri($uri);
698
+			if ($result) {
699
+				return $result;
700
+			}
701
+		}
702
+	}
703
+
704
+	/**
705
+	 * Principal property search.
706
+	 *
707
+	 * This method can search for principals matching certain values in
708
+	 * properties.
709
+	 *
710
+	 * This method will return a list of properties for the matched properties.
711
+	 *
712
+	 * @param array  $searchProperties    The properties to search on. This is a
713
+	 *                                    key-value list. The keys are property
714
+	 *                                    names, and the values the strings to
715
+	 *                                    match them on.
716
+	 * @param array  $requestedProperties this is the list of properties to
717
+	 *                                    return for every match
718
+	 * @param string $collectionUri       the principal collection to search on.
719
+	 *                                    If this is ommitted, the standard
720
+	 *                                    principal collection-set will be used
721
+	 * @param string $test                "allof" to use AND to search the
722
+	 *                                    properties. 'anyof' for OR.
723
+	 *
724
+	 * @return array This method returns an array structure similar to
725
+	 *               Sabre\DAV\Server::getPropertiesForPath. Returned
726
+	 *               properties are index by a HTTP status code.
727
+	 */
728
+	public function principalSearch(array $searchProperties, array $requestedProperties, $collectionUri = null, $test = 'allof')
729
+	{
730
+		if (!is_null($collectionUri)) {
731
+			$uris = [$collectionUri];
732
+		} else {
733
+			$uris = $this->principalCollectionSet;
734
+		}
735
+
736
+		$lookupResults = [];
737
+		foreach ($uris as $uri) {
738
+			$principalCollection = $this->server->tree->getNodeForPath($uri);
739
+			if (!$principalCollection instanceof IPrincipalCollection) {
740
+				// Not a principal collection, we're simply going to ignore
741
+				// this.
742
+				continue;
743
+			}
744
+
745
+			$results = $principalCollection->searchPrincipals($searchProperties, $test);
746
+			foreach ($results as $result) {
747
+				$lookupResults[] = rtrim($uri, '/').'/'.$result;
748
+			}
749
+		}
750
+
751
+		$matches = [];
752
+
753
+		foreach ($lookupResults as $lookupResult) {
754
+			list($matches[]) = $this->server->getPropertiesForPath($lookupResult, $requestedProperties, 0);
755
+		}
756
+
757
+		return $matches;
758
+	}
759
+
760
+	/**
761
+	 * Sets up the plugin.
762
+	 *
763
+	 * This method is automatically called by the server class.
764
+	 */
765
+	public function initialize(DAV\Server $server)
766
+	{
767
+		if ($this->allowUnauthenticatedAccess) {
768
+			$authPlugin = $server->getPlugin('auth');
769
+			if (!$authPlugin) {
770
+				throw new \Exception('The Auth plugin must be loaded before the ACL plugin if you want to allow unauthenticated access.');
771
+			}
772
+			$authPlugin->autoRequireLogin = false;
773
+		}
774
+
775
+		$this->server = $server;
776
+		$server->on('propFind', [$this, 'propFind'], 20);
777
+		$server->on('beforeMethod:*', [$this, 'beforeMethod'], 20);
778
+		$server->on('beforeBind', [$this, 'beforeBind'], 20);
779
+		$server->on('beforeUnbind', [$this, 'beforeUnbind'], 20);
780
+		$server->on('propPatch', [$this, 'propPatch']);
781
+		$server->on('beforeUnlock', [$this, 'beforeUnlock'], 20);
782
+		$server->on('report', [$this, 'report']);
783
+		$server->on('method:ACL', [$this, 'httpAcl']);
784
+		$server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']);
785
+		$server->on('getPrincipalByUri', function ($principal, &$uri) {
786
+			$uri = $this->getPrincipalByUri($principal);
787
+
788
+			// Break event chain
789
+			if ($uri) {
790
+				return false;
791
+			}
792
+		});
793
+
794
+		array_push($server->protectedProperties,
795
+			'{DAV:}alternate-URI-set',
796
+			'{DAV:}principal-URL',
797
+			'{DAV:}group-membership',
798
+			'{DAV:}principal-collection-set',
799
+			'{DAV:}current-user-principal',
800
+			'{DAV:}supported-privilege-set',
801
+			'{DAV:}current-user-privilege-set',
802
+			'{DAV:}acl',
803
+			'{DAV:}acl-restrictions',
804
+			'{DAV:}inherited-acl-set',
805
+			'{DAV:}owner',
806
+			'{DAV:}group'
807
+		);
808
+
809
+		// Automatically mapping nodes implementing IPrincipal to the
810
+		// {DAV:}principal resourcetype.
811
+		$server->resourceTypeMapping['Sabre\\DAVACL\\IPrincipal'] = '{DAV:}principal';
812
+
813
+		// Mapping the group-member-set property to the HrefList property
814
+		// class.
815
+		$server->xml->elementMap['{DAV:}group-member-set'] = 'Sabre\\DAV\\Xml\\Property\\Href';
816
+		$server->xml->elementMap['{DAV:}acl'] = 'Sabre\\DAVACL\\Xml\\Property\\Acl';
817
+		$server->xml->elementMap['{DAV:}acl-principal-prop-set'] = 'Sabre\\DAVACL\\Xml\\Request\\AclPrincipalPropSetReport';
818
+		$server->xml->elementMap['{DAV:}expand-property'] = 'Sabre\\DAVACL\\Xml\\Request\\ExpandPropertyReport';
819
+		$server->xml->elementMap['{DAV:}principal-property-search'] = 'Sabre\\DAVACL\\Xml\\Request\\PrincipalPropertySearchReport';
820
+		$server->xml->elementMap['{DAV:}principal-search-property-set'] = 'Sabre\\DAVACL\\Xml\\Request\\PrincipalSearchPropertySetReport';
821
+		$server->xml->elementMap['{DAV:}principal-match'] = 'Sabre\\DAVACL\\Xml\\Request\\PrincipalMatchReport';
822
+	}
823
+
824
+	/* {{{ Event handlers */
825
+
826
+	/**
827
+	 * Triggered before any method is handled.
828
+	 */
829
+	public function beforeMethod(RequestInterface $request, ResponseInterface $response)
830
+	{
831
+		$method = $request->getMethod();
832
+		$path = $request->getPath();
833
+
834
+		$exists = $this->server->tree->nodeExists($path);
835
+
836
+		// If the node doesn't exists, none of these checks apply
837
+		if (!$exists) {
838
+			return;
839
+		}
840
+
841
+		switch ($method) {
842
+			case 'GET':
843
+			case 'HEAD':
844
+			case 'OPTIONS':
845
+				// For these 3 we only need to know if the node is readable.
846
+				$this->checkPrivileges($path, '{DAV:}read');
847
+				break;
848
+
849
+			case 'PUT':
850
+			case 'LOCK':
851
+				// This method requires the write-content priv if the node
852
+				// already exists, and bind on the parent if the node is being
853
+				// created.
854
+				// The bind privilege is handled in the beforeBind event.
855
+				$this->checkPrivileges($path, '{DAV:}write-content');
856
+				break;
857
+
858
+			case 'UNLOCK':
859
+				// Unlock is always allowed at the moment.
860
+				break;
861
+
862
+			case 'PROPPATCH':
863
+				$this->checkPrivileges($path, '{DAV:}write-properties');
864
+				break;
865
+
866
+			case 'ACL':
867
+				$this->checkPrivileges($path, '{DAV:}write-acl');
868
+				break;
869
+
870
+			case 'COPY':
871
+			case 'MOVE':
872
+				// Copy requires read privileges on the entire source tree.
873
+				// If the target exists write-content normally needs to be
874
+				// checked, however, we're deleting the node beforehand and
875
+				// creating a new one after, so this is handled by the
876
+				// beforeUnbind event.
877
+				//
878
+				// The creation of the new node is handled by the beforeBind
879
+				// event.
880
+				//
881
+				// If MOVE is used beforeUnbind will also be used to check if
882
+				// the sourcenode can be deleted.
883
+				$this->checkPrivileges($path, '{DAV:}read', self::R_RECURSIVE);
884
+				break;
885
+		}
886
+	}
887
+
888
+	/**
889
+	 * Triggered before a new node is created.
890
+	 *
891
+	 * This allows us to check permissions for any operation that creates a
892
+	 * new node, such as PUT, MKCOL, MKCALENDAR, LOCK, COPY and MOVE.
893
+	 *
894
+	 * @param string $uri
895
+	 */
896
+	public function beforeBind($uri)
897
+	{
898
+		list($parentUri) = Uri\split($uri);
899
+		$this->checkPrivileges($parentUri, '{DAV:}bind');
900
+	}
901
+
902
+	/**
903
+	 * Triggered before a node is deleted.
904
+	 *
905
+	 * This allows us to check permissions for any operation that will delete
906
+	 * an existing node.
907
+	 *
908
+	 * @param string $uri
909
+	 */
910
+	public function beforeUnbind($uri)
911
+	{
912
+		list($parentUri) = Uri\split($uri);
913
+		$this->checkPrivileges($parentUri, '{DAV:}unbind', self::R_RECURSIVEPARENTS);
914
+	}
915
+
916
+	/**
917
+	 * Triggered before a node is unlocked.
918
+	 *
919
+	 * @param string $uri
920
+	 * @TODO: not yet implemented
921
+	 */
922
+	public function beforeUnlock($uri, DAV\Locks\LockInfo $lock)
923
+	{
924
+	}
925
+
926
+	/**
927
+	 * Triggered before properties are looked up in specific nodes.
928
+	 *
929
+	 * @TODO really should be broken into multiple methods, or even a class.
930
+	 */
931
+	public function propFind(DAV\PropFind $propFind, DAV\INode $node)
932
+	{
933
+		$path = $propFind->getPath();
934
+
935
+		// Checking the read permission
936
+		if (!$this->checkPrivileges($path, '{DAV:}read', self::R_PARENT, false)) {
937
+			// User is not allowed to read properties
938
+
939
+			// Returning false causes the property-fetching system to pretend
940
+			// that the node does not exist, and will cause it to be hidden
941
+			// from listings such as PROPFIND or the browser plugin.
942
+			if ($this->hideNodesFromListings) {
943
+				return false;
944
+			}
945
+
946
+			// Otherwise we simply mark every property as 403.
947
+			foreach ($propFind->getRequestedProperties() as $requestedProperty) {
948
+				$propFind->set($requestedProperty, null, 403);
949
+			}
950
+
951
+			return;
952
+		}
953
+
954
+		/* Adding principal properties */
955
+		if ($node instanceof IPrincipal) {
956
+			$propFind->handle('{DAV:}alternate-URI-set', function () use ($node) {
957
+				return new Href($node->getAlternateUriSet());
958
+			});
959
+			$propFind->handle('{DAV:}principal-URL', function () use ($node) {
960
+				return new Href($node->getPrincipalUrl().'/');
961
+			});
962
+			$propFind->handle('{DAV:}group-member-set', function () use ($node) {
963
+				$members = $node->getGroupMemberSet();
964
+				foreach ($members as $k => $member) {
965
+					$members[$k] = rtrim($member, '/').'/';
966
+				}
967
+
968
+				return new Href($members);
969
+			});
970
+			$propFind->handle('{DAV:}group-membership', function () use ($node) {
971
+				$members = $node->getGroupMembership();
972
+				foreach ($members as $k => $member) {
973
+					$members[$k] = rtrim($member, '/').'/';
974
+				}
975
+
976
+				return new Href($members);
977
+			});
978
+			$propFind->handle('{DAV:}displayname', [$node, 'getDisplayName']);
979
+		}
980
+
981
+		$propFind->handle('{DAV:}principal-collection-set', function () {
982
+			$val = $this->principalCollectionSet;
983
+			// Ensuring all collections end with a slash
984
+			foreach ($val as $k => $v) {
985
+				$val[$k] = $v.'/';
986
+			}
987
+
988
+			return new Href($val);
989
+		});
990
+		$propFind->handle('{DAV:}current-user-principal', function () {
991
+			if ($url = $this->getCurrentUserPrincipal()) {
992
+				return new Xml\Property\Principal(Xml\Property\Principal::HREF, $url.'/');
993
+			} else {
994
+				return new Xml\Property\Principal(Xml\Property\Principal::UNAUTHENTICATED);
995
+			}
996
+		});
997
+		$propFind->handle('{DAV:}supported-privilege-set', function () use ($node) {
998
+			return new Xml\Property\SupportedPrivilegeSet($this->getSupportedPrivilegeSet($node));
999
+		});
1000
+		$propFind->handle('{DAV:}current-user-privilege-set', function () use ($node, $propFind, $path) {
1001
+			if (!$this->checkPrivileges($path, '{DAV:}read-current-user-privilege-set', self::R_PARENT, false)) {
1002
+				$propFind->set('{DAV:}current-user-privilege-set', null, 403);
1003
+			} else {
1004
+				$val = $this->getCurrentUserPrivilegeSet($node);
1005
+
1006
+				return new Xml\Property\CurrentUserPrivilegeSet($val);
1007
+			}
1008
+		});
1009
+		$propFind->handle('{DAV:}acl', function () use ($node, $propFind, $path) {
1010
+			/* The ACL property contains all the permissions */
1011
+			if (!$this->checkPrivileges($path, '{DAV:}read-acl', self::R_PARENT, false)) {
1012
+				$propFind->set('{DAV:}acl', null, 403);
1013
+			} else {
1014
+				$acl = $this->getACL($node);
1015
+
1016
+				return new Xml\Property\Acl($this->getACL($node));
1017
+			}
1018
+		});
1019
+		$propFind->handle('{DAV:}acl-restrictions', function () {
1020
+			return new Xml\Property\AclRestrictions();
1021
+		});
1022
+
1023
+		/* Adding ACL properties */
1024
+		if ($node instanceof IACL) {
1025
+			$propFind->handle('{DAV:}owner', function () use ($node) {
1026
+				return new Href($node->getOwner().'/');
1027
+			});
1028
+		}
1029
+	}
1030
+
1031
+	/**
1032
+	 * This method intercepts PROPPATCH methods and make sure the
1033
+	 * group-member-set is updated correctly.
1034
+	 *
1035
+	 * @param string $path
1036
+	 */
1037
+	public function propPatch($path, DAV\PropPatch $propPatch)
1038
+	{
1039
+		$propPatch->handle('{DAV:}group-member-set', function ($value) use ($path) {
1040
+			if (is_null($value)) {
1041
+				$memberSet = [];
1042
+			} elseif ($value instanceof Href) {
1043
+				$memberSet = array_map(
1044
+					[$this->server, 'calculateUri'],
1045
+					$value->getHrefs()
1046
+				);
1047
+			} else {
1048
+				throw new DAV\Exception('The group-member-set property MUST be an instance of Sabre\DAV\Property\HrefList or null');
1049
+			}
1050
+			$node = $this->server->tree->getNodeForPath($path);
1051
+			if (!($node instanceof IPrincipal)) {
1052
+				// Fail
1053
+				return false;
1054
+			}
1055
+
1056
+			$node->setGroupMemberSet($memberSet);
1057
+			// We must also clear our cache, just in case
1058
+
1059
+			$this->principalMembershipCache = [];
1060
+
1061
+			return true;
1062
+		});
1063
+	}
1064
+
1065
+	/**
1066
+	 * This method handles HTTP REPORT requests.
1067
+	 *
1068
+	 * @param string $reportName
1069
+	 * @param mixed  $report
1070
+	 * @param mixed  $path
1071
+	 */
1072
+	public function report($reportName, $report, $path)
1073
+	{
1074
+		switch ($reportName) {
1075
+			case '{DAV:}principal-property-search':
1076
+				$this->server->transactionType = 'report-principal-property-search';
1077
+				$this->principalPropertySearchReport($path, $report);
1078
+
1079
+				return false;
1080
+			case '{DAV:}principal-search-property-set':
1081
+				$this->server->transactionType = 'report-principal-search-property-set';
1082
+				$this->principalSearchPropertySetReport($path, $report);
1083
+
1084
+				return false;
1085
+			case '{DAV:}expand-property':
1086
+				$this->server->transactionType = 'report-expand-property';
1087
+				$this->expandPropertyReport($path, $report);
1088
+
1089
+				return false;
1090
+			case '{DAV:}principal-match':
1091
+				$this->server->transactionType = 'report-principal-match';
1092
+				$this->principalMatchReport($path, $report);
1093
+
1094
+				return false;
1095
+			case '{DAV:}acl-principal-prop-set':
1096
+				$this->server->transactionType = 'acl-principal-prop-set';
1097
+				$this->aclPrincipalPropSetReport($path, $report);
1098
+
1099
+				return false;
1100
+		}
1101
+	}
1102
+
1103
+	/**
1104
+	 * This method is responsible for handling the 'ACL' event.
1105
+	 *
1106
+	 * @return bool
1107
+	 */
1108
+	public function httpAcl(RequestInterface $request, ResponseInterface $response)
1109
+	{
1110
+		$path = $request->getPath();
1111
+		$body = $request->getBodyAsString();
1112
+
1113
+		if (!$body) {
1114
+			throw new DAV\Exception\BadRequest('XML body expected in ACL request');
1115
+		}
1116
+
1117
+		$acl = $this->server->xml->expect('{DAV:}acl', $body);
1118
+		$newAcl = $acl->getPrivileges();
1119
+
1120
+		// Normalizing urls
1121
+		foreach ($newAcl as $k => $newAce) {
1122
+			$newAcl[$k]['principal'] = $this->server->calculateUri($newAce['principal']);
1123
+		}
1124
+		$node = $this->server->tree->getNodeForPath($path);
1125
+
1126
+		if (!$node instanceof IACL) {
1127
+			throw new DAV\Exception\MethodNotAllowed('This node does not support the ACL method');
1128
+		}
1129
+
1130
+		$oldAcl = $this->getACL($node);
1131
+
1132
+		$supportedPrivileges = $this->getFlatPrivilegeSet($node);
1133
+
1134
+		/* Checking if protected principals from the existing principal set are
1135 1135
            not overwritten. */
1136
-        foreach ($oldAcl as $oldAce) {
1137
-            if (!isset($oldAce['protected']) || !$oldAce['protected']) {
1138
-                continue;
1139
-            }
1140
-
1141
-            $found = false;
1142
-            foreach ($newAcl as $newAce) {
1143
-                if (
1144
-                    $newAce['privilege'] === $oldAce['privilege'] &&
1145
-                    $newAce['principal'] === $oldAce['principal'] &&
1146
-                    $newAce['protected']
1147
-                ) {
1148
-                    $found = true;
1149
-                }
1150
-            }
1151
-
1152
-            if (!$found) {
1153
-                throw new Exception\AceConflict('This resource contained a protected {DAV:}ace, but this privilege did not occur in the ACL request');
1154
-            }
1155
-        }
1156
-
1157
-        foreach ($newAcl as $newAce) {
1158
-            // Do we recognize the privilege
1159
-            if (!isset($supportedPrivileges[$newAce['privilege']])) {
1160
-                throw new Exception\NotSupportedPrivilege('The privilege you specified ('.$newAce['privilege'].') is not recognized by this server');
1161
-            }
1162
-
1163
-            if ($supportedPrivileges[$newAce['privilege']]['abstract']) {
1164
-                throw new Exception\NoAbstract('The privilege you specified ('.$newAce['privilege'].') is an abstract privilege');
1165
-            }
1166
-
1167
-            // Looking up the principal
1168
-            try {
1169
-                $principal = $this->server->tree->getNodeForPath($newAce['principal']);
1170
-            } catch (NotFound $e) {
1171
-                throw new Exception\NotRecognizedPrincipal('The specified principal ('.$newAce['principal'].') does not exist');
1172
-            }
1173
-            if (!($principal instanceof IPrincipal)) {
1174
-                throw new Exception\NotRecognizedPrincipal('The specified uri ('.$newAce['principal'].') is not a principal');
1175
-            }
1176
-        }
1177
-        $node->setACL($newAcl);
1178
-
1179
-        $response->setStatus(200);
1180
-
1181
-        // Breaking the event chain, because we handled this method.
1182
-        return false;
1183
-    }
1184
-
1185
-    /* }}} */
1186
-
1187
-    /* Reports {{{ */
1188
-
1189
-    /**
1190
-     * The principal-match report is defined in RFC3744, section 9.3.
1191
-     *
1192
-     * This report allows a client to figure out based on the current user,
1193
-     * or a principal URL, the principal URL and principal URLs of groups that
1194
-     * principal belongs to.
1195
-     *
1196
-     * @param string $path
1197
-     */
1198
-    protected function principalMatchReport($path, Xml\Request\PrincipalMatchReport $report)
1199
-    {
1200
-        $depth = $this->server->getHTTPDepth(0);
1201
-        if (0 !== $depth) {
1202
-            throw new BadRequest('The principal-match report is only defined on Depth: 0');
1203
-        }
1204
-
1205
-        $currentPrincipals = $this->getCurrentUserPrincipals();
1206
-
1207
-        $result = [];
1208
-
1209
-        if (Xml\Request\PrincipalMatchReport::SELF === $report->type) {
1210
-            // Finding all principals under the request uri that match the
1211
-            // current principal.
1212
-            foreach ($currentPrincipals as $currentPrincipal) {
1213
-                if ($currentPrincipal === $path || 0 === strpos($currentPrincipal, $path.'/')) {
1214
-                    $result[] = $currentPrincipal;
1215
-                }
1216
-            }
1217
-        } else {
1218
-            // We need to find all resources that have a property that matches
1219
-            // one of the current principals.
1220
-            $candidates = $this->server->getPropertiesForPath(
1221
-                $path,
1222
-                [$report->principalProperty],
1223
-                1
1224
-            );
1225
-
1226
-            foreach ($candidates as $candidate) {
1227
-                if (!isset($candidate[200][$report->principalProperty])) {
1228
-                    continue;
1229
-                }
1230
-
1231
-                $hrefs = $candidate[200][$report->principalProperty];
1232
-
1233
-                if (!$hrefs instanceof Href) {
1234
-                    continue;
1235
-                }
1236
-
1237
-                foreach ($hrefs->getHrefs() as $href) {
1238
-                    if (in_array(trim($href, '/'), $currentPrincipals)) {
1239
-                        $result[] = $candidate['href'];
1240
-                        continue 2;
1241
-                    }
1242
-                }
1243
-            }
1244
-        }
1245
-
1246
-        $responses = [];
1247
-
1248
-        foreach ($result as $item) {
1249
-            $properties = [];
1250
-
1251
-            if ($report->properties) {
1252
-                $foo = $this->server->getPropertiesForPath($item, $report->properties);
1253
-                $foo = $foo[0];
1254
-                $item = $foo['href'];
1255
-                unset($foo['href']);
1256
-                $properties = $foo;
1257
-            }
1258
-
1259
-            $responses[] = new DAV\Xml\Element\Response(
1260
-                $item,
1261
-                $properties,
1262
-                '200'
1263
-            );
1264
-        }
1265
-
1266
-        $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
1267
-        $this->server->httpResponse->setStatus(207);
1268
-        $this->server->httpResponse->setBody(
1269
-            $this->server->xml->write(
1270
-                '{DAV:}multistatus',
1271
-                $responses,
1272
-                $this->server->getBaseUri()
1273
-            )
1274
-        );
1275
-    }
1276
-
1277
-    /**
1278
-     * The expand-property report is defined in RFC3253 section 3.8.
1279
-     *
1280
-     * This report is very similar to a standard PROPFIND. The difference is
1281
-     * that it has the additional ability to look at properties containing a
1282
-     * {DAV:}href element, follow that property and grab additional elements
1283
-     * there.
1284
-     *
1285
-     * Other rfc's, such as ACL rely on this report, so it made sense to put
1286
-     * it in this plugin.
1287
-     *
1288
-     * @param string                           $path
1289
-     * @param Xml\Request\ExpandPropertyReport $report
1290
-     */
1291
-    protected function expandPropertyReport($path, $report)
1292
-    {
1293
-        $depth = $this->server->getHTTPDepth(0);
1294
-
1295
-        $result = $this->expandProperties($path, $report->properties, $depth);
1296
-
1297
-        $xml = $this->server->xml->write(
1298
-            '{DAV:}multistatus',
1299
-            new DAV\Xml\Response\MultiStatus($result),
1300
-            $this->server->getBaseUri()
1301
-        );
1302
-        $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
1303
-        $this->server->httpResponse->setStatus(207);
1304
-        $this->server->httpResponse->setBody($xml);
1305
-    }
1306
-
1307
-    /**
1308
-     * This method expands all the properties and returns
1309
-     * a list with property values.
1310
-     *
1311
-     * @param array $path
1312
-     * @param array $requestedProperties the list of required properties
1313
-     * @param int   $depth
1314
-     *
1315
-     * @return array
1316
-     */
1317
-    protected function expandProperties($path, array $requestedProperties, $depth)
1318
-    {
1319
-        $foundProperties = $this->server->getPropertiesForPath($path, array_keys($requestedProperties), $depth);
1320
-
1321
-        $result = [];
1322
-
1323
-        foreach ($foundProperties as $node) {
1324
-            foreach ($requestedProperties as $propertyName => $childRequestedProperties) {
1325
-                // We're only traversing if sub-properties were requested
1326
-                if (!is_array($childRequestedProperties) || 0 === count($childRequestedProperties)) {
1327
-                    continue;
1328
-                }
1329
-
1330
-                // We only have to do the expansion if the property was found
1331
-                // and it contains an href element.
1332
-                if (!array_key_exists($propertyName, $node[200])) {
1333
-                    continue;
1334
-                }
1335
-
1336
-                if (!$node[200][$propertyName] instanceof DAV\Xml\Property\Href) {
1337
-                    continue;
1338
-                }
1339
-
1340
-                $childHrefs = $node[200][$propertyName]->getHrefs();
1341
-                $childProps = [];
1342
-
1343
-                foreach ($childHrefs as $href) {
1344
-                    // Gathering the result of the children
1345
-                    $childProps[] = [
1346
-                        'name' => '{DAV:}response',
1347
-                        'value' => $this->expandProperties($href, $childRequestedProperties, 0)[0],
1348
-                    ];
1349
-                }
1350
-
1351
-                // Replacing the property with its expanded form.
1352
-                $node[200][$propertyName] = $childProps;
1353
-            }
1354
-            $result[] = new DAV\Xml\Element\Response($node['href'], $node);
1355
-        }
1356
-
1357
-        return $result;
1358
-    }
1359
-
1360
-    /**
1361
-     * principalSearchPropertySetReport.
1362
-     *
1363
-     * This method responsible for handing the
1364
-     * {DAV:}principal-search-property-set report. This report returns a list
1365
-     * of properties the client may search on, using the
1366
-     * {DAV:}principal-property-search report.
1367
-     *
1368
-     * @param string                                       $path
1369
-     * @param Xml\Request\PrincipalSearchPropertySetReport $report
1370
-     */
1371
-    protected function principalSearchPropertySetReport($path, $report)
1372
-    {
1373
-        $httpDepth = $this->server->getHTTPDepth(0);
1374
-        if (0 !== $httpDepth) {
1375
-            throw new DAV\Exception\BadRequest('This report is only defined when Depth: 0');
1376
-        }
1377
-
1378
-        $writer = $this->server->xml->getWriter();
1379
-        $writer->openMemory();
1380
-        $writer->startDocument();
1381
-
1382
-        $writer->startElement('{DAV:}principal-search-property-set');
1383
-
1384
-        foreach ($this->principalSearchPropertySet as $propertyName => $description) {
1385
-            $writer->startElement('{DAV:}principal-search-property');
1386
-            $writer->startElement('{DAV:}prop');
1387
-
1388
-            $writer->writeElement($propertyName);
1389
-
1390
-            $writer->endElement(); // prop
1391
-
1392
-            if ($description) {
1393
-                $writer->write([[
1394
-                    'name' => '{DAV:}description',
1395
-                    'value' => $description,
1396
-                    'attributes' => ['xml:lang' => 'en'],
1397
-                ]]);
1398
-            }
1399
-
1400
-            $writer->endElement(); // principal-search-property
1401
-        }
1402
-
1403
-        $writer->endElement(); // principal-search-property-set
1404
-
1405
-        $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
1406
-        $this->server->httpResponse->setStatus(200);
1407
-        $this->server->httpResponse->setBody($writer->outputMemory());
1408
-    }
1409
-
1410
-    /**
1411
-     * principalPropertySearchReport.
1412
-     *
1413
-     * This method is responsible for handing the
1414
-     * {DAV:}principal-property-search report. This report can be used for
1415
-     * clients to search for groups of principals, based on the value of one
1416
-     * or more properties.
1417
-     *
1418
-     * @param string $path
1419
-     */
1420
-    protected function principalPropertySearchReport($path, Xml\Request\PrincipalPropertySearchReport $report)
1421
-    {
1422
-        if ($report->applyToPrincipalCollectionSet) {
1423
-            $path = null;
1424
-        }
1425
-        if (0 !== $this->server->getHttpDepth('0')) {
1426
-            throw new BadRequest('Depth must be 0');
1427
-        }
1428
-        $result = $this->principalSearch(
1429
-            $report->searchProperties,
1430
-            $report->properties,
1431
-            $path,
1432
-            $report->test
1433
-        );
1434
-
1435
-        $prefer = $this->server->getHTTPPrefer();
1436
-
1437
-        $this->server->httpResponse->setStatus(207);
1438
-        $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
1439
-        $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
1440
-        $this->server->httpResponse->setBody($this->server->generateMultiStatus($result, 'minimal' === $prefer['return']));
1441
-    }
1442
-
1443
-    /**
1444
-     * aclPrincipalPropSet REPORT.
1445
-     *
1446
-     * This method is responsible for handling the {DAV:}acl-principal-prop-set
1447
-     * REPORT, as defined in:
1448
-     *
1449
-     * https://tools.ietf.org/html/rfc3744#section-9.2
1450
-     *
1451
-     * This REPORT allows a user to quickly fetch information about all
1452
-     * principals specified in the access control list. Most commonly this
1453
-     * is used to for example generate a UI with ACL rules, allowing you
1454
-     * to show names for principals for every entry.
1455
-     *
1456
-     * @param string $path
1457
-     */
1458
-    protected function aclPrincipalPropSetReport($path, Xml\Request\AclPrincipalPropSetReport $report)
1459
-    {
1460
-        if (0 !== $this->server->getHTTPDepth(0)) {
1461
-            throw new BadRequest('The {DAV:}acl-principal-prop-set REPORT only supports Depth 0');
1462
-        }
1463
-
1464
-        // Fetching ACL rules for the given path. We're using the property
1465
-        // API and not the local getACL, because it will ensure that all
1466
-        // business rules and restrictions are applied.
1467
-        $acl = $this->server->getProperties($path, '{DAV:}acl');
1468
-
1469
-        if (!$acl || !isset($acl['{DAV:}acl'])) {
1470
-            throw new Forbidden('Could not fetch ACL rules for this path');
1471
-        }
1472
-
1473
-        $principals = [];
1474
-        foreach ($acl['{DAV:}acl']->getPrivileges() as $ace) {
1475
-            if ('{' === $ace['principal'][0]) {
1476
-                // It's not a principal, it's one of the special rules such as {DAV:}authenticated
1477
-                continue;
1478
-            }
1479
-
1480
-            $principals[] = $ace['principal'];
1481
-        }
1482
-
1483
-        $properties = $this->server->getPropertiesForMultiplePaths(
1484
-            $principals,
1485
-            $report->properties
1486
-        );
1487
-
1488
-        $this->server->httpResponse->setStatus(207);
1489
-        $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
1490
-        $this->server->httpResponse->setBody(
1491
-            $this->server->generateMultiStatus($properties)
1492
-        );
1493
-    }
1494
-
1495
-    /* }}} */
1496
-
1497
-    /**
1498
-     * This method is used to generate HTML output for the
1499
-     * DAV\Browser\Plugin. This allows us to generate an interface users
1500
-     * can use to create new calendars.
1501
-     *
1502
-     * @param string $output
1503
-     *
1504
-     * @return bool
1505
-     */
1506
-    public function htmlActionsPanel(DAV\INode $node, &$output)
1507
-    {
1508
-        if (!$node instanceof PrincipalCollection) {
1509
-            return;
1510
-        }
1511
-
1512
-        $output .= '<tr><td colspan="2"><form method="post" action="">
1136
+		foreach ($oldAcl as $oldAce) {
1137
+			if (!isset($oldAce['protected']) || !$oldAce['protected']) {
1138
+				continue;
1139
+			}
1140
+
1141
+			$found = false;
1142
+			foreach ($newAcl as $newAce) {
1143
+				if (
1144
+					$newAce['privilege'] === $oldAce['privilege'] &&
1145
+					$newAce['principal'] === $oldAce['principal'] &&
1146
+					$newAce['protected']
1147
+				) {
1148
+					$found = true;
1149
+				}
1150
+			}
1151
+
1152
+			if (!$found) {
1153
+				throw new Exception\AceConflict('This resource contained a protected {DAV:}ace, but this privilege did not occur in the ACL request');
1154
+			}
1155
+		}
1156
+
1157
+		foreach ($newAcl as $newAce) {
1158
+			// Do we recognize the privilege
1159
+			if (!isset($supportedPrivileges[$newAce['privilege']])) {
1160
+				throw new Exception\NotSupportedPrivilege('The privilege you specified ('.$newAce['privilege'].') is not recognized by this server');
1161
+			}
1162
+
1163
+			if ($supportedPrivileges[$newAce['privilege']]['abstract']) {
1164
+				throw new Exception\NoAbstract('The privilege you specified ('.$newAce['privilege'].') is an abstract privilege');
1165
+			}
1166
+
1167
+			// Looking up the principal
1168
+			try {
1169
+				$principal = $this->server->tree->getNodeForPath($newAce['principal']);
1170
+			} catch (NotFound $e) {
1171
+				throw new Exception\NotRecognizedPrincipal('The specified principal ('.$newAce['principal'].') does not exist');
1172
+			}
1173
+			if (!($principal instanceof IPrincipal)) {
1174
+				throw new Exception\NotRecognizedPrincipal('The specified uri ('.$newAce['principal'].') is not a principal');
1175
+			}
1176
+		}
1177
+		$node->setACL($newAcl);
1178
+
1179
+		$response->setStatus(200);
1180
+
1181
+		// Breaking the event chain, because we handled this method.
1182
+		return false;
1183
+	}
1184
+
1185
+	/* }}} */
1186
+
1187
+	/* Reports {{{ */
1188
+
1189
+	/**
1190
+	 * The principal-match report is defined in RFC3744, section 9.3.
1191
+	 *
1192
+	 * This report allows a client to figure out based on the current user,
1193
+	 * or a principal URL, the principal URL and principal URLs of groups that
1194
+	 * principal belongs to.
1195
+	 *
1196
+	 * @param string $path
1197
+	 */
1198
+	protected function principalMatchReport($path, Xml\Request\PrincipalMatchReport $report)
1199
+	{
1200
+		$depth = $this->server->getHTTPDepth(0);
1201
+		if (0 !== $depth) {
1202
+			throw new BadRequest('The principal-match report is only defined on Depth: 0');
1203
+		}
1204
+
1205
+		$currentPrincipals = $this->getCurrentUserPrincipals();
1206
+
1207
+		$result = [];
1208
+
1209
+		if (Xml\Request\PrincipalMatchReport::SELF === $report->type) {
1210
+			// Finding all principals under the request uri that match the
1211
+			// current principal.
1212
+			foreach ($currentPrincipals as $currentPrincipal) {
1213
+				if ($currentPrincipal === $path || 0 === strpos($currentPrincipal, $path.'/')) {
1214
+					$result[] = $currentPrincipal;
1215
+				}
1216
+			}
1217
+		} else {
1218
+			// We need to find all resources that have a property that matches
1219
+			// one of the current principals.
1220
+			$candidates = $this->server->getPropertiesForPath(
1221
+				$path,
1222
+				[$report->principalProperty],
1223
+				1
1224
+			);
1225
+
1226
+			foreach ($candidates as $candidate) {
1227
+				if (!isset($candidate[200][$report->principalProperty])) {
1228
+					continue;
1229
+				}
1230
+
1231
+				$hrefs = $candidate[200][$report->principalProperty];
1232
+
1233
+				if (!$hrefs instanceof Href) {
1234
+					continue;
1235
+				}
1236
+
1237
+				foreach ($hrefs->getHrefs() as $href) {
1238
+					if (in_array(trim($href, '/'), $currentPrincipals)) {
1239
+						$result[] = $candidate['href'];
1240
+						continue 2;
1241
+					}
1242
+				}
1243
+			}
1244
+		}
1245
+
1246
+		$responses = [];
1247
+
1248
+		foreach ($result as $item) {
1249
+			$properties = [];
1250
+
1251
+			if ($report->properties) {
1252
+				$foo = $this->server->getPropertiesForPath($item, $report->properties);
1253
+				$foo = $foo[0];
1254
+				$item = $foo['href'];
1255
+				unset($foo['href']);
1256
+				$properties = $foo;
1257
+			}
1258
+
1259
+			$responses[] = new DAV\Xml\Element\Response(
1260
+				$item,
1261
+				$properties,
1262
+				'200'
1263
+			);
1264
+		}
1265
+
1266
+		$this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
1267
+		$this->server->httpResponse->setStatus(207);
1268
+		$this->server->httpResponse->setBody(
1269
+			$this->server->xml->write(
1270
+				'{DAV:}multistatus',
1271
+				$responses,
1272
+				$this->server->getBaseUri()
1273
+			)
1274
+		);
1275
+	}
1276
+
1277
+	/**
1278
+	 * The expand-property report is defined in RFC3253 section 3.8.
1279
+	 *
1280
+	 * This report is very similar to a standard PROPFIND. The difference is
1281
+	 * that it has the additional ability to look at properties containing a
1282
+	 * {DAV:}href element, follow that property and grab additional elements
1283
+	 * there.
1284
+	 *
1285
+	 * Other rfc's, such as ACL rely on this report, so it made sense to put
1286
+	 * it in this plugin.
1287
+	 *
1288
+	 * @param string                           $path
1289
+	 * @param Xml\Request\ExpandPropertyReport $report
1290
+	 */
1291
+	protected function expandPropertyReport($path, $report)
1292
+	{
1293
+		$depth = $this->server->getHTTPDepth(0);
1294
+
1295
+		$result = $this->expandProperties($path, $report->properties, $depth);
1296
+
1297
+		$xml = $this->server->xml->write(
1298
+			'{DAV:}multistatus',
1299
+			new DAV\Xml\Response\MultiStatus($result),
1300
+			$this->server->getBaseUri()
1301
+		);
1302
+		$this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
1303
+		$this->server->httpResponse->setStatus(207);
1304
+		$this->server->httpResponse->setBody($xml);
1305
+	}
1306
+
1307
+	/**
1308
+	 * This method expands all the properties and returns
1309
+	 * a list with property values.
1310
+	 *
1311
+	 * @param array $path
1312
+	 * @param array $requestedProperties the list of required properties
1313
+	 * @param int   $depth
1314
+	 *
1315
+	 * @return array
1316
+	 */
1317
+	protected function expandProperties($path, array $requestedProperties, $depth)
1318
+	{
1319
+		$foundProperties = $this->server->getPropertiesForPath($path, array_keys($requestedProperties), $depth);
1320
+
1321
+		$result = [];
1322
+
1323
+		foreach ($foundProperties as $node) {
1324
+			foreach ($requestedProperties as $propertyName => $childRequestedProperties) {
1325
+				// We're only traversing if sub-properties were requested
1326
+				if (!is_array($childRequestedProperties) || 0 === count($childRequestedProperties)) {
1327
+					continue;
1328
+				}
1329
+
1330
+				// We only have to do the expansion if the property was found
1331
+				// and it contains an href element.
1332
+				if (!array_key_exists($propertyName, $node[200])) {
1333
+					continue;
1334
+				}
1335
+
1336
+				if (!$node[200][$propertyName] instanceof DAV\Xml\Property\Href) {
1337
+					continue;
1338
+				}
1339
+
1340
+				$childHrefs = $node[200][$propertyName]->getHrefs();
1341
+				$childProps = [];
1342
+
1343
+				foreach ($childHrefs as $href) {
1344
+					// Gathering the result of the children
1345
+					$childProps[] = [
1346
+						'name' => '{DAV:}response',
1347
+						'value' => $this->expandProperties($href, $childRequestedProperties, 0)[0],
1348
+					];
1349
+				}
1350
+
1351
+				// Replacing the property with its expanded form.
1352
+				$node[200][$propertyName] = $childProps;
1353
+			}
1354
+			$result[] = new DAV\Xml\Element\Response($node['href'], $node);
1355
+		}
1356
+
1357
+		return $result;
1358
+	}
1359
+
1360
+	/**
1361
+	 * principalSearchPropertySetReport.
1362
+	 *
1363
+	 * This method responsible for handing the
1364
+	 * {DAV:}principal-search-property-set report. This report returns a list
1365
+	 * of properties the client may search on, using the
1366
+	 * {DAV:}principal-property-search report.
1367
+	 *
1368
+	 * @param string                                       $path
1369
+	 * @param Xml\Request\PrincipalSearchPropertySetReport $report
1370
+	 */
1371
+	protected function principalSearchPropertySetReport($path, $report)
1372
+	{
1373
+		$httpDepth = $this->server->getHTTPDepth(0);
1374
+		if (0 !== $httpDepth) {
1375
+			throw new DAV\Exception\BadRequest('This report is only defined when Depth: 0');
1376
+		}
1377
+
1378
+		$writer = $this->server->xml->getWriter();
1379
+		$writer->openMemory();
1380
+		$writer->startDocument();
1381
+
1382
+		$writer->startElement('{DAV:}principal-search-property-set');
1383
+
1384
+		foreach ($this->principalSearchPropertySet as $propertyName => $description) {
1385
+			$writer->startElement('{DAV:}principal-search-property');
1386
+			$writer->startElement('{DAV:}prop');
1387
+
1388
+			$writer->writeElement($propertyName);
1389
+
1390
+			$writer->endElement(); // prop
1391
+
1392
+			if ($description) {
1393
+				$writer->write([[
1394
+					'name' => '{DAV:}description',
1395
+					'value' => $description,
1396
+					'attributes' => ['xml:lang' => 'en'],
1397
+				]]);
1398
+			}
1399
+
1400
+			$writer->endElement(); // principal-search-property
1401
+		}
1402
+
1403
+		$writer->endElement(); // principal-search-property-set
1404
+
1405
+		$this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
1406
+		$this->server->httpResponse->setStatus(200);
1407
+		$this->server->httpResponse->setBody($writer->outputMemory());
1408
+	}
1409
+
1410
+	/**
1411
+	 * principalPropertySearchReport.
1412
+	 *
1413
+	 * This method is responsible for handing the
1414
+	 * {DAV:}principal-property-search report. This report can be used for
1415
+	 * clients to search for groups of principals, based on the value of one
1416
+	 * or more properties.
1417
+	 *
1418
+	 * @param string $path
1419
+	 */
1420
+	protected function principalPropertySearchReport($path, Xml\Request\PrincipalPropertySearchReport $report)
1421
+	{
1422
+		if ($report->applyToPrincipalCollectionSet) {
1423
+			$path = null;
1424
+		}
1425
+		if (0 !== $this->server->getHttpDepth('0')) {
1426
+			throw new BadRequest('Depth must be 0');
1427
+		}
1428
+		$result = $this->principalSearch(
1429
+			$report->searchProperties,
1430
+			$report->properties,
1431
+			$path,
1432
+			$report->test
1433
+		);
1434
+
1435
+		$prefer = $this->server->getHTTPPrefer();
1436
+
1437
+		$this->server->httpResponse->setStatus(207);
1438
+		$this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
1439
+		$this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
1440
+		$this->server->httpResponse->setBody($this->server->generateMultiStatus($result, 'minimal' === $prefer['return']));
1441
+	}
1442
+
1443
+	/**
1444
+	 * aclPrincipalPropSet REPORT.
1445
+	 *
1446
+	 * This method is responsible for handling the {DAV:}acl-principal-prop-set
1447
+	 * REPORT, as defined in:
1448
+	 *
1449
+	 * https://tools.ietf.org/html/rfc3744#section-9.2
1450
+	 *
1451
+	 * This REPORT allows a user to quickly fetch information about all
1452
+	 * principals specified in the access control list. Most commonly this
1453
+	 * is used to for example generate a UI with ACL rules, allowing you
1454
+	 * to show names for principals for every entry.
1455
+	 *
1456
+	 * @param string $path
1457
+	 */
1458
+	protected function aclPrincipalPropSetReport($path, Xml\Request\AclPrincipalPropSetReport $report)
1459
+	{
1460
+		if (0 !== $this->server->getHTTPDepth(0)) {
1461
+			throw new BadRequest('The {DAV:}acl-principal-prop-set REPORT only supports Depth 0');
1462
+		}
1463
+
1464
+		// Fetching ACL rules for the given path. We're using the property
1465
+		// API and not the local getACL, because it will ensure that all
1466
+		// business rules and restrictions are applied.
1467
+		$acl = $this->server->getProperties($path, '{DAV:}acl');
1468
+
1469
+		if (!$acl || !isset($acl['{DAV:}acl'])) {
1470
+			throw new Forbidden('Could not fetch ACL rules for this path');
1471
+		}
1472
+
1473
+		$principals = [];
1474
+		foreach ($acl['{DAV:}acl']->getPrivileges() as $ace) {
1475
+			if ('{' === $ace['principal'][0]) {
1476
+				// It's not a principal, it's one of the special rules such as {DAV:}authenticated
1477
+				continue;
1478
+			}
1479
+
1480
+			$principals[] = $ace['principal'];
1481
+		}
1482
+
1483
+		$properties = $this->server->getPropertiesForMultiplePaths(
1484
+			$principals,
1485
+			$report->properties
1486
+		);
1487
+
1488
+		$this->server->httpResponse->setStatus(207);
1489
+		$this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
1490
+		$this->server->httpResponse->setBody(
1491
+			$this->server->generateMultiStatus($properties)
1492
+		);
1493
+	}
1494
+
1495
+	/* }}} */
1496
+
1497
+	/**
1498
+	 * This method is used to generate HTML output for the
1499
+	 * DAV\Browser\Plugin. This allows us to generate an interface users
1500
+	 * can use to create new calendars.
1501
+	 *
1502
+	 * @param string $output
1503
+	 *
1504
+	 * @return bool
1505
+	 */
1506
+	public function htmlActionsPanel(DAV\INode $node, &$output)
1507
+	{
1508
+		if (!$node instanceof PrincipalCollection) {
1509
+			return;
1510
+		}
1511
+
1512
+		$output .= '<tr><td colspan="2"><form method="post" action="">
1513 1513
             <h3>Create new principal</h3>
1514 1514
             <input type="hidden" name="sabreAction" value="mkcol" />
1515 1515
             <input type="hidden" name="resourceType" value="{DAV:}principal" />
@@ -1520,26 +1520,26 @@  discard block
 block discarded – undo
1520 1520
             </form>
1521 1521
             </td></tr>';
1522 1522
 
1523
-        return false;
1524
-    }
1525
-
1526
-    /**
1527
-     * Returns a bunch of meta-data about the plugin.
1528
-     *
1529
-     * Providing this information is optional, and is mainly displayed by the
1530
-     * Browser plugin.
1531
-     *
1532
-     * The description key in the returned array may contain html and will not
1533
-     * be sanitized.
1534
-     *
1535
-     * @return array
1536
-     */
1537
-    public function getPluginInfo()
1538
-    {
1539
-        return [
1540
-            'name' => $this->getPluginName(),
1541
-            'description' => 'Adds support for WebDAV ACL (rfc3744)',
1542
-            'link' => 'http://sabre.io/dav/acl/',
1543
-        ];
1544
-    }
1523
+		return false;
1524
+	}
1525
+
1526
+	/**
1527
+	 * Returns a bunch of meta-data about the plugin.
1528
+	 *
1529
+	 * Providing this information is optional, and is mainly displayed by the
1530
+	 * Browser plugin.
1531
+	 *
1532
+	 * The description key in the returned array may contain html and will not
1533
+	 * be sanitized.
1534
+	 *
1535
+	 * @return array
1536
+	 */
1537
+	public function getPluginInfo()
1538
+	{
1539
+		return [
1540
+			'name' => $this->getPluginName(),
1541
+			'description' => 'Adds support for WebDAV ACL (rfc3744)',
1542
+			'link' => 'http://sabre.io/dav/acl/',
1543
+		];
1544
+	}
1545 1545
 }
Please login to merge, or discard this patch.
Spacing   +14 added lines, -14 removed lines patch added patch discarded remove patch
@@ -523,7 +523,7 @@  discard block
 block discarded – undo
523 523
         ];
524 524
 
525 525
         $fpsTraverse = null;
526
-        $fpsTraverse = function ($privName, $privInfo, $concrete, &$flat) use (&$fpsTraverse) {
526
+        $fpsTraverse = function($privName, $privInfo, $concrete, &$flat) use (&$fpsTraverse) {
527 527
             $myPriv = [
528 528
                 'privilege' => $privName,
529 529
                 'abstract' => isset($privInfo['abstract']) && $privInfo['abstract'],
@@ -782,7 +782,7 @@  discard block
 block discarded – undo
782 782
         $server->on('report', [$this, 'report']);
783 783
         $server->on('method:ACL', [$this, 'httpAcl']);
784 784
         $server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']);
785
-        $server->on('getPrincipalByUri', function ($principal, &$uri) {
785
+        $server->on('getPrincipalByUri', function($principal, &$uri) {
786 786
             $uri = $this->getPrincipalByUri($principal);
787 787
 
788 788
             // Break event chain
@@ -953,13 +953,13 @@  discard block
 block discarded – undo
953 953
 
954 954
         /* Adding principal properties */
955 955
         if ($node instanceof IPrincipal) {
956
-            $propFind->handle('{DAV:}alternate-URI-set', function () use ($node) {
956
+            $propFind->handle('{DAV:}alternate-URI-set', function() use ($node) {
957 957
                 return new Href($node->getAlternateUriSet());
958 958
             });
959
-            $propFind->handle('{DAV:}principal-URL', function () use ($node) {
959
+            $propFind->handle('{DAV:}principal-URL', function() use ($node) {
960 960
                 return new Href($node->getPrincipalUrl().'/');
961 961
             });
962
-            $propFind->handle('{DAV:}group-member-set', function () use ($node) {
962
+            $propFind->handle('{DAV:}group-member-set', function() use ($node) {
963 963
                 $members = $node->getGroupMemberSet();
964 964
                 foreach ($members as $k => $member) {
965 965
                     $members[$k] = rtrim($member, '/').'/';
@@ -967,7 +967,7 @@  discard block
 block discarded – undo
967 967
 
968 968
                 return new Href($members);
969 969
             });
970
-            $propFind->handle('{DAV:}group-membership', function () use ($node) {
970
+            $propFind->handle('{DAV:}group-membership', function() use ($node) {
971 971
                 $members = $node->getGroupMembership();
972 972
                 foreach ($members as $k => $member) {
973 973
                     $members[$k] = rtrim($member, '/').'/';
@@ -978,7 +978,7 @@  discard block
 block discarded – undo
978 978
             $propFind->handle('{DAV:}displayname', [$node, 'getDisplayName']);
979 979
         }
980 980
 
981
-        $propFind->handle('{DAV:}principal-collection-set', function () {
981
+        $propFind->handle('{DAV:}principal-collection-set', function() {
982 982
             $val = $this->principalCollectionSet;
983 983
             // Ensuring all collections end with a slash
984 984
             foreach ($val as $k => $v) {
@@ -987,17 +987,17 @@  discard block
 block discarded – undo
987 987
 
988 988
             return new Href($val);
989 989
         });
990
-        $propFind->handle('{DAV:}current-user-principal', function () {
990
+        $propFind->handle('{DAV:}current-user-principal', function() {
991 991
             if ($url = $this->getCurrentUserPrincipal()) {
992 992
                 return new Xml\Property\Principal(Xml\Property\Principal::HREF, $url.'/');
993 993
             } else {
994 994
                 return new Xml\Property\Principal(Xml\Property\Principal::UNAUTHENTICATED);
995 995
             }
996 996
         });
997
-        $propFind->handle('{DAV:}supported-privilege-set', function () use ($node) {
997
+        $propFind->handle('{DAV:}supported-privilege-set', function() use ($node) {
998 998
             return new Xml\Property\SupportedPrivilegeSet($this->getSupportedPrivilegeSet($node));
999 999
         });
1000
-        $propFind->handle('{DAV:}current-user-privilege-set', function () use ($node, $propFind, $path) {
1000
+        $propFind->handle('{DAV:}current-user-privilege-set', function() use ($node, $propFind, $path) {
1001 1001
             if (!$this->checkPrivileges($path, '{DAV:}read-current-user-privilege-set', self::R_PARENT, false)) {
1002 1002
                 $propFind->set('{DAV:}current-user-privilege-set', null, 403);
1003 1003
             } else {
@@ -1006,7 +1006,7 @@  discard block
 block discarded – undo
1006 1006
                 return new Xml\Property\CurrentUserPrivilegeSet($val);
1007 1007
             }
1008 1008
         });
1009
-        $propFind->handle('{DAV:}acl', function () use ($node, $propFind, $path) {
1009
+        $propFind->handle('{DAV:}acl', function() use ($node, $propFind, $path) {
1010 1010
             /* The ACL property contains all the permissions */
1011 1011
             if (!$this->checkPrivileges($path, '{DAV:}read-acl', self::R_PARENT, false)) {
1012 1012
                 $propFind->set('{DAV:}acl', null, 403);
@@ -1016,13 +1016,13 @@  discard block
 block discarded – undo
1016 1016
                 return new Xml\Property\Acl($this->getACL($node));
1017 1017
             }
1018 1018
         });
1019
-        $propFind->handle('{DAV:}acl-restrictions', function () {
1019
+        $propFind->handle('{DAV:}acl-restrictions', function() {
1020 1020
             return new Xml\Property\AclRestrictions();
1021 1021
         });
1022 1022
 
1023 1023
         /* Adding ACL properties */
1024 1024
         if ($node instanceof IACL) {
1025
-            $propFind->handle('{DAV:}owner', function () use ($node) {
1025
+            $propFind->handle('{DAV:}owner', function() use ($node) {
1026 1026
                 return new Href($node->getOwner().'/');
1027 1027
             });
1028 1028
         }
@@ -1036,7 +1036,7 @@  discard block
 block discarded – undo
1036 1036
      */
1037 1037
     public function propPatch($path, DAV\PropPatch $propPatch)
1038 1038
     {
1039
-        $propPatch->handle('{DAV:}group-member-set', function ($value) use ($path) {
1039
+        $propPatch->handle('{DAV:}group-member-set', function($value) use ($path) {
1040 1040
             if (is_null($value)) {
1041 1041
                 $memberSet = [];
1042 1042
             } elseif ($value instanceof Href) {
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/dav/lib/DAVACL/FS/File.php 1 patch
Indentation   +53 added lines, -53 removed lines patch added patch discarded remove patch
@@ -17,62 +17,62 @@
 block discarded – undo
17 17
  */
18 18
 class File extends BaseFile implements IACL
19 19
 {
20
-    use ACLTrait;
20
+	use ACLTrait;
21 21
 
22
-    /**
23
-     * A list of ACL rules.
24
-     *
25
-     * @var array
26
-     */
27
-    protected $acl;
22
+	/**
23
+	 * A list of ACL rules.
24
+	 *
25
+	 * @var array
26
+	 */
27
+	protected $acl;
28 28
 
29
-    /**
30
-     * Owner uri, or null for no owner.
31
-     *
32
-     * @var string|null
33
-     */
34
-    protected $owner;
29
+	/**
30
+	 * Owner uri, or null for no owner.
31
+	 *
32
+	 * @var string|null
33
+	 */
34
+	protected $owner;
35 35
 
36
-    /**
37
-     * Constructor.
38
-     *
39
-     * @param string      $path  on-disk path
40
-     * @param array       $acl   ACL rules
41
-     * @param string|null $owner principal owner string
42
-     */
43
-    public function __construct($path, array $acl, $owner = null)
44
-    {
45
-        parent::__construct($path);
46
-        $this->acl = $acl;
47
-        $this->owner = $owner;
48
-    }
36
+	/**
37
+	 * Constructor.
38
+	 *
39
+	 * @param string      $path  on-disk path
40
+	 * @param array       $acl   ACL rules
41
+	 * @param string|null $owner principal owner string
42
+	 */
43
+	public function __construct($path, array $acl, $owner = null)
44
+	{
45
+		parent::__construct($path);
46
+		$this->acl = $acl;
47
+		$this->owner = $owner;
48
+	}
49 49
 
50
-    /**
51
-     * Returns the owner principal.
52
-     *
53
-     * This must be a url to a principal, or null if there's no owner
54
-     *
55
-     * @return string|null
56
-     */
57
-    public function getOwner()
58
-    {
59
-        return $this->owner;
60
-    }
50
+	/**
51
+	 * Returns the owner principal.
52
+	 *
53
+	 * This must be a url to a principal, or null if there's no owner
54
+	 *
55
+	 * @return string|null
56
+	 */
57
+	public function getOwner()
58
+	{
59
+		return $this->owner;
60
+	}
61 61
 
62
-    /**
63
-     * Returns a list of ACE's for this node.
64
-     *
65
-     * Each ACE has the following properties:
66
-     *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
67
-     *     currently the only supported privileges
68
-     *   * 'principal', a url to the principal who owns the node
69
-     *   * 'protected' (optional), indicating that this ACE is not allowed to
70
-     *      be updated.
71
-     *
72
-     * @return array
73
-     */
74
-    public function getACL()
75
-    {
76
-        return $this->acl;
77
-    }
62
+	/**
63
+	 * Returns a list of ACE's for this node.
64
+	 *
65
+	 * Each ACE has the following properties:
66
+	 *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
67
+	 *     currently the only supported privileges
68
+	 *   * 'principal', a url to the principal who owns the node
69
+	 *   * 'protected' (optional), indicating that this ACE is not allowed to
70
+	 *      be updated.
71
+	 *
72
+	 * @return array
73
+	 */
74
+	public function getACL()
75
+	{
76
+		return $this->acl;
77
+	}
78 78
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/dav/lib/DAVACL/FS/Collection.php 1 patch
Indentation   +80 added lines, -80 removed lines patch added patch discarded remove patch
@@ -19,91 +19,91 @@
 block discarded – undo
19 19
  */
20 20
 class Collection extends BaseCollection implements IACL
21 21
 {
22
-    use ACLTrait;
22
+	use ACLTrait;
23 23
 
24
-    /**
25
-     * A list of ACL rules.
26
-     *
27
-     * @var array
28
-     */
29
-    protected $acl;
24
+	/**
25
+	 * A list of ACL rules.
26
+	 *
27
+	 * @var array
28
+	 */
29
+	protected $acl;
30 30
 
31
-    /**
32
-     * Owner uri, or null for no owner.
33
-     *
34
-     * @var string|null
35
-     */
36
-    protected $owner;
31
+	/**
32
+	 * Owner uri, or null for no owner.
33
+	 *
34
+	 * @var string|null
35
+	 */
36
+	protected $owner;
37 37
 
38
-    /**
39
-     * Constructor.
40
-     *
41
-     * @param string      $path  on-disk path
42
-     * @param array       $acl   ACL rules
43
-     * @param string|null $owner principal owner string
44
-     */
45
-    public function __construct($path, array $acl, $owner = null)
46
-    {
47
-        parent::__construct($path);
48
-        $this->acl = $acl;
49
-        $this->owner = $owner;
50
-    }
38
+	/**
39
+	 * Constructor.
40
+	 *
41
+	 * @param string      $path  on-disk path
42
+	 * @param array       $acl   ACL rules
43
+	 * @param string|null $owner principal owner string
44
+	 */
45
+	public function __construct($path, array $acl, $owner = null)
46
+	{
47
+		parent::__construct($path);
48
+		$this->acl = $acl;
49
+		$this->owner = $owner;
50
+	}
51 51
 
52
-    /**
53
-     * Returns a specific child node, referenced by its name.
54
-     *
55
-     * This method must throw Sabre\DAV\Exception\NotFound if the node does not
56
-     * exist.
57
-     *
58
-     * @param string $name
59
-     *
60
-     * @throws NotFound
61
-     *
62
-     * @return \Sabre\DAV\INode
63
-     */
64
-    public function getChild($name)
65
-    {
66
-        $path = $this->path.'/'.$name;
52
+	/**
53
+	 * Returns a specific child node, referenced by its name.
54
+	 *
55
+	 * This method must throw Sabre\DAV\Exception\NotFound if the node does not
56
+	 * exist.
57
+	 *
58
+	 * @param string $name
59
+	 *
60
+	 * @throws NotFound
61
+	 *
62
+	 * @return \Sabre\DAV\INode
63
+	 */
64
+	public function getChild($name)
65
+	{
66
+		$path = $this->path.'/'.$name;
67 67
 
68
-        if (!file_exists($path)) {
69
-            throw new NotFound('File could not be located');
70
-        }
71
-        if ('.' == $name || '..' == $name) {
72
-            throw new Forbidden('Permission denied to . and ..');
73
-        }
74
-        if (is_dir($path)) {
75
-            return new self($path, $this->acl, $this->owner);
76
-        } else {
77
-            return new File($path, $this->acl, $this->owner);
78
-        }
79
-    }
68
+		if (!file_exists($path)) {
69
+			throw new NotFound('File could not be located');
70
+		}
71
+		if ('.' == $name || '..' == $name) {
72
+			throw new Forbidden('Permission denied to . and ..');
73
+		}
74
+		if (is_dir($path)) {
75
+			return new self($path, $this->acl, $this->owner);
76
+		} else {
77
+			return new File($path, $this->acl, $this->owner);
78
+		}
79
+	}
80 80
 
81
-    /**
82
-     * Returns the owner principal.
83
-     *
84
-     * This must be a url to a principal, or null if there's no owner
85
-     *
86
-     * @return string|null
87
-     */
88
-    public function getOwner()
89
-    {
90
-        return $this->owner;
91
-    }
81
+	/**
82
+	 * Returns the owner principal.
83
+	 *
84
+	 * This must be a url to a principal, or null if there's no owner
85
+	 *
86
+	 * @return string|null
87
+	 */
88
+	public function getOwner()
89
+	{
90
+		return $this->owner;
91
+	}
92 92
 
93
-    /**
94
-     * Returns a list of ACE's for this node.
95
-     *
96
-     * Each ACE has the following properties:
97
-     *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
98
-     *     currently the only supported privileges
99
-     *   * 'principal', a url to the principal who owns the node
100
-     *   * 'protected' (optional), indicating that this ACE is not allowed to
101
-     *      be updated.
102
-     *
103
-     * @return array
104
-     */
105
-    public function getACL()
106
-    {
107
-        return $this->acl;
108
-    }
93
+	/**
94
+	 * Returns a list of ACE's for this node.
95
+	 *
96
+	 * Each ACE has the following properties:
97
+	 *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
98
+	 *     currently the only supported privileges
99
+	 *   * 'principal', a url to the principal who owns the node
100
+	 *   * 'protected' (optional), indicating that this ACE is not allowed to
101
+	 *      be updated.
102
+	 *
103
+	 * @return array
104
+	 */
105
+	public function getACL()
106
+	{
107
+		return $this->acl;
108
+	}
109 109
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/dav/lib/DAVACL/FS/HomeCollection.php 1 patch
Indentation   +87 added lines, -87 removed lines patch added patch discarded remove patch
@@ -23,101 +23,101 @@
 block discarded – undo
23 23
  */
24 24
 class HomeCollection extends AbstractPrincipalCollection implements IACL
25 25
 {
26
-    use ACLTrait;
26
+	use ACLTrait;
27 27
 
28
-    /**
29
-     * Name of this collection.
30
-     *
31
-     * @var string
32
-     */
33
-    public $collectionName = 'home';
28
+	/**
29
+	 * Name of this collection.
30
+	 *
31
+	 * @var string
32
+	 */
33
+	public $collectionName = 'home';
34 34
 
35
-    /**
36
-     * Path to where the users' files are actually stored.
37
-     *
38
-     * @var string
39
-     */
40
-    protected $storagePath;
35
+	/**
36
+	 * Path to where the users' files are actually stored.
37
+	 *
38
+	 * @var string
39
+	 */
40
+	protected $storagePath;
41 41
 
42
-    /**
43
-     * Creates the home collection.
44
-     *
45
-     * @param string $storagePath     where the actual files are stored
46
-     * @param string $principalPrefix list of principals to iterate
47
-     */
48
-    public function __construct(BackendInterface $principalBackend, $storagePath, $principalPrefix = 'principals')
49
-    {
50
-        parent::__construct($principalBackend, $principalPrefix);
51
-        $this->storagePath = $storagePath;
52
-    }
42
+	/**
43
+	 * Creates the home collection.
44
+	 *
45
+	 * @param string $storagePath     where the actual files are stored
46
+	 * @param string $principalPrefix list of principals to iterate
47
+	 */
48
+	public function __construct(BackendInterface $principalBackend, $storagePath, $principalPrefix = 'principals')
49
+	{
50
+		parent::__construct($principalBackend, $principalPrefix);
51
+		$this->storagePath = $storagePath;
52
+	}
53 53
 
54
-    /**
55
-     * Returns the name of the node.
56
-     *
57
-     * This is used to generate the url.
58
-     *
59
-     * @return string
60
-     */
61
-    public function getName()
62
-    {
63
-        return $this->collectionName;
64
-    }
54
+	/**
55
+	 * Returns the name of the node.
56
+	 *
57
+	 * This is used to generate the url.
58
+	 *
59
+	 * @return string
60
+	 */
61
+	public function getName()
62
+	{
63
+		return $this->collectionName;
64
+	}
65 65
 
66
-    /**
67
-     * Returns a principals' collection of files.
68
-     *
69
-     * The passed array contains principal information, and is guaranteed to
70
-     * at least contain a uri item. Other properties may or may not be
71
-     * supplied by the authentication backend.
72
-     *
73
-     * @return \Sabre\DAV\INode
74
-     */
75
-    public function getChildForPrincipal(array $principalInfo)
76
-    {
77
-        $owner = $principalInfo['uri'];
78
-        $acl = [
79
-            [
80
-                'privilege' => '{DAV:}all',
81
-                'principal' => '{DAV:}owner',
82
-                'protected' => true,
83
-            ],
84
-        ];
66
+	/**
67
+	 * Returns a principals' collection of files.
68
+	 *
69
+	 * The passed array contains principal information, and is guaranteed to
70
+	 * at least contain a uri item. Other properties may or may not be
71
+	 * supplied by the authentication backend.
72
+	 *
73
+	 * @return \Sabre\DAV\INode
74
+	 */
75
+	public function getChildForPrincipal(array $principalInfo)
76
+	{
77
+		$owner = $principalInfo['uri'];
78
+		$acl = [
79
+			[
80
+				'privilege' => '{DAV:}all',
81
+				'principal' => '{DAV:}owner',
82
+				'protected' => true,
83
+			],
84
+		];
85 85
 
86
-        list(, $principalBaseName) = Uri\split($owner);
86
+		list(, $principalBaseName) = Uri\split($owner);
87 87
 
88
-        $path = $this->storagePath.'/'.$principalBaseName;
88
+		$path = $this->storagePath.'/'.$principalBaseName;
89 89
 
90
-        if (!is_dir($path)) {
91
-            mkdir($path, 0777, true);
92
-        }
90
+		if (!is_dir($path)) {
91
+			mkdir($path, 0777, true);
92
+		}
93 93
 
94
-        return new Collection(
95
-            $path,
96
-            $acl,
97
-            $owner
98
-        );
99
-    }
94
+		return new Collection(
95
+			$path,
96
+			$acl,
97
+			$owner
98
+		);
99
+	}
100 100
 
101
-    /**
102
-     * Returns a list of ACE's for this node.
103
-     *
104
-     * Each ACE has the following properties:
105
-     *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
106
-     *     currently the only supported privileges
107
-     *   * 'principal', a url to the principal who owns the node
108
-     *   * 'protected' (optional), indicating that this ACE is not allowed to
109
-     *      be updated.
110
-     *
111
-     * @return array
112
-     */
113
-    public function getACL()
114
-    {
115
-        return [
116
-            [
117
-                'principal' => '{DAV:}authenticated',
118
-                'privilege' => '{DAV:}read',
119
-                'protected' => true,
120
-            ],
121
-        ];
122
-    }
101
+	/**
102
+	 * Returns a list of ACE's for this node.
103
+	 *
104
+	 * Each ACE has the following properties:
105
+	 *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
106
+	 *     currently the only supported privileges
107
+	 *   * 'principal', a url to the principal who owns the node
108
+	 *   * 'protected' (optional), indicating that this ACE is not allowed to
109
+	 *      be updated.
110
+	 *
111
+	 * @return array
112
+	 */
113
+	public function getACL()
114
+	{
115
+		return [
116
+			[
117
+				'principal' => '{DAV:}authenticated',
118
+				'privilege' => '{DAV:}read',
119
+				'protected' => true,
120
+			],
121
+		];
122
+	}
123 123
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/dav/lib/DAVACL/AbstractPrincipalCollection.php 1 patch
Indentation   +154 added lines, -154 removed lines patch added patch discarded remove patch
@@ -21,158 +21,158 @@
 block discarded – undo
21 21
  */
22 22
 abstract class AbstractPrincipalCollection extends DAV\Collection implements IPrincipalCollection
23 23
 {
24
-    /**
25
-     * Principal backend.
26
-     *
27
-     * @var PrincipalBackend\BackendInterface
28
-     */
29
-    protected $principalBackend;
30
-
31
-    /**
32
-     * The path to the principals we're listing from.
33
-     *
34
-     * @var string
35
-     */
36
-    protected $principalPrefix;
37
-
38
-    /**
39
-     * If this value is set to true, it effectively disables listing of users
40
-     * it still allows user to find other users if they have an exact url.
41
-     *
42
-     * @var bool
43
-     */
44
-    public $disableListing = false;
45
-
46
-    /**
47
-     * Creates the object.
48
-     *
49
-     * This object must be passed the principal backend. This object will
50
-     * filter all principals from a specified prefix ($principalPrefix). The
51
-     * default is 'principals', if your principals are stored in a different
52
-     * collection, override $principalPrefix
53
-     *
54
-     * @param string $principalPrefix
55
-     */
56
-    public function __construct(PrincipalBackend\BackendInterface $principalBackend, $principalPrefix = 'principals')
57
-    {
58
-        $this->principalPrefix = $principalPrefix;
59
-        $this->principalBackend = $principalBackend;
60
-    }
61
-
62
-    /**
63
-     * This method returns a node for a principal.
64
-     *
65
-     * The passed array contains principal information, and is guaranteed to
66
-     * at least contain a uri item. Other properties may or may not be
67
-     * supplied by the authentication backend.
68
-     *
69
-     * @return DAV\INode
70
-     */
71
-    abstract public function getChildForPrincipal(array $principalInfo);
72
-
73
-    /**
74
-     * Returns the name of this collection.
75
-     *
76
-     * @return string
77
-     */
78
-    public function getName()
79
-    {
80
-        list(, $name) = Uri\split($this->principalPrefix);
81
-
82
-        return $name;
83
-    }
84
-
85
-    /**
86
-     * Return the list of users.
87
-     *
88
-     * @return array
89
-     */
90
-    public function getChildren()
91
-    {
92
-        if ($this->disableListing) {
93
-            throw new DAV\Exception\MethodNotAllowed('Listing members of this collection is disabled');
94
-        }
95
-        $children = [];
96
-        foreach ($this->principalBackend->getPrincipalsByPrefix($this->principalPrefix) as $principalInfo) {
97
-            $children[] = $this->getChildForPrincipal($principalInfo);
98
-        }
99
-
100
-        return $children;
101
-    }
102
-
103
-    /**
104
-     * Returns a child object, by its name.
105
-     *
106
-     * @param string $name
107
-     *
108
-     * @throws DAV\Exception\NotFound
109
-     *
110
-     * @return DAV\INode
111
-     */
112
-    public function getChild($name)
113
-    {
114
-        $principalInfo = $this->principalBackend->getPrincipalByPath($this->principalPrefix.'/'.$name);
115
-        if (!$principalInfo) {
116
-            throw new DAV\Exception\NotFound('Principal with name '.$name.' not found');
117
-        }
118
-
119
-        return $this->getChildForPrincipal($principalInfo);
120
-    }
121
-
122
-    /**
123
-     * This method is used to search for principals matching a set of
124
-     * properties.
125
-     *
126
-     * This search is specifically used by RFC3744's principal-property-search
127
-     * REPORT. You should at least allow searching on
128
-     * http://sabredav.org/ns}email-address.
129
-     *
130
-     * The actual search should be a unicode-non-case-sensitive search. The
131
-     * keys in searchProperties are the WebDAV property names, while the values
132
-     * are the property values to search on.
133
-     *
134
-     * By default, if multiple properties are submitted to this method, the
135
-     * various properties should be combined with 'AND'. If $test is set to
136
-     * 'anyof', it should be combined using 'OR'.
137
-     *
138
-     * This method should simply return a list of 'child names', which may be
139
-     * used to call $this->getChild in the future.
140
-     *
141
-     * @param string $test
142
-     *
143
-     * @return array
144
-     */
145
-    public function searchPrincipals(array $searchProperties, $test = 'allof')
146
-    {
147
-        $result = $this->principalBackend->searchPrincipals($this->principalPrefix, $searchProperties, $test);
148
-        $r = [];
149
-
150
-        foreach ($result as $row) {
151
-            list(, $r[]) = Uri\split($row);
152
-        }
153
-
154
-        return $r;
155
-    }
156
-
157
-    /**
158
-     * Finds a principal by its URI.
159
-     *
160
-     * This method may receive any type of uri, but mailto: addresses will be
161
-     * the most common.
162
-     *
163
-     * Implementation of this API is optional. It is currently used by the
164
-     * CalDAV system to find principals based on their email addresses. If this
165
-     * API is not implemented, some features may not work correctly.
166
-     *
167
-     * This method must return a relative principal path, or null, if the
168
-     * principal was not found or you refuse to find it.
169
-     *
170
-     * @param string $uri
171
-     *
172
-     * @return string
173
-     */
174
-    public function findByUri($uri)
175
-    {
176
-        return $this->principalBackend->findByUri($uri, $this->principalPrefix);
177
-    }
24
+	/**
25
+	 * Principal backend.
26
+	 *
27
+	 * @var PrincipalBackend\BackendInterface
28
+	 */
29
+	protected $principalBackend;
30
+
31
+	/**
32
+	 * The path to the principals we're listing from.
33
+	 *
34
+	 * @var string
35
+	 */
36
+	protected $principalPrefix;
37
+
38
+	/**
39
+	 * If this value is set to true, it effectively disables listing of users
40
+	 * it still allows user to find other users if they have an exact url.
41
+	 *
42
+	 * @var bool
43
+	 */
44
+	public $disableListing = false;
45
+
46
+	/**
47
+	 * Creates the object.
48
+	 *
49
+	 * This object must be passed the principal backend. This object will
50
+	 * filter all principals from a specified prefix ($principalPrefix). The
51
+	 * default is 'principals', if your principals are stored in a different
52
+	 * collection, override $principalPrefix
53
+	 *
54
+	 * @param string $principalPrefix
55
+	 */
56
+	public function __construct(PrincipalBackend\BackendInterface $principalBackend, $principalPrefix = 'principals')
57
+	{
58
+		$this->principalPrefix = $principalPrefix;
59
+		$this->principalBackend = $principalBackend;
60
+	}
61
+
62
+	/**
63
+	 * This method returns a node for a principal.
64
+	 *
65
+	 * The passed array contains principal information, and is guaranteed to
66
+	 * at least contain a uri item. Other properties may or may not be
67
+	 * supplied by the authentication backend.
68
+	 *
69
+	 * @return DAV\INode
70
+	 */
71
+	abstract public function getChildForPrincipal(array $principalInfo);
72
+
73
+	/**
74
+	 * Returns the name of this collection.
75
+	 *
76
+	 * @return string
77
+	 */
78
+	public function getName()
79
+	{
80
+		list(, $name) = Uri\split($this->principalPrefix);
81
+
82
+		return $name;
83
+	}
84
+
85
+	/**
86
+	 * Return the list of users.
87
+	 *
88
+	 * @return array
89
+	 */
90
+	public function getChildren()
91
+	{
92
+		if ($this->disableListing) {
93
+			throw new DAV\Exception\MethodNotAllowed('Listing members of this collection is disabled');
94
+		}
95
+		$children = [];
96
+		foreach ($this->principalBackend->getPrincipalsByPrefix($this->principalPrefix) as $principalInfo) {
97
+			$children[] = $this->getChildForPrincipal($principalInfo);
98
+		}
99
+
100
+		return $children;
101
+	}
102
+
103
+	/**
104
+	 * Returns a child object, by its name.
105
+	 *
106
+	 * @param string $name
107
+	 *
108
+	 * @throws DAV\Exception\NotFound
109
+	 *
110
+	 * @return DAV\INode
111
+	 */
112
+	public function getChild($name)
113
+	{
114
+		$principalInfo = $this->principalBackend->getPrincipalByPath($this->principalPrefix.'/'.$name);
115
+		if (!$principalInfo) {
116
+			throw new DAV\Exception\NotFound('Principal with name '.$name.' not found');
117
+		}
118
+
119
+		return $this->getChildForPrincipal($principalInfo);
120
+	}
121
+
122
+	/**
123
+	 * This method is used to search for principals matching a set of
124
+	 * properties.
125
+	 *
126
+	 * This search is specifically used by RFC3744's principal-property-search
127
+	 * REPORT. You should at least allow searching on
128
+	 * http://sabredav.org/ns}email-address.
129
+	 *
130
+	 * The actual search should be a unicode-non-case-sensitive search. The
131
+	 * keys in searchProperties are the WebDAV property names, while the values
132
+	 * are the property values to search on.
133
+	 *
134
+	 * By default, if multiple properties are submitted to this method, the
135
+	 * various properties should be combined with 'AND'. If $test is set to
136
+	 * 'anyof', it should be combined using 'OR'.
137
+	 *
138
+	 * This method should simply return a list of 'child names', which may be
139
+	 * used to call $this->getChild in the future.
140
+	 *
141
+	 * @param string $test
142
+	 *
143
+	 * @return array
144
+	 */
145
+	public function searchPrincipals(array $searchProperties, $test = 'allof')
146
+	{
147
+		$result = $this->principalBackend->searchPrincipals($this->principalPrefix, $searchProperties, $test);
148
+		$r = [];
149
+
150
+		foreach ($result as $row) {
151
+			list(, $r[]) = Uri\split($row);
152
+		}
153
+
154
+		return $r;
155
+	}
156
+
157
+	/**
158
+	 * Finds a principal by its URI.
159
+	 *
160
+	 * This method may receive any type of uri, but mailto: addresses will be
161
+	 * the most common.
162
+	 *
163
+	 * Implementation of this API is optional. It is currently used by the
164
+	 * CalDAV system to find principals based on their email addresses. If this
165
+	 * API is not implemented, some features may not work correctly.
166
+	 *
167
+	 * This method must return a relative principal path, or null, if the
168
+	 * principal was not found or you refuse to find it.
169
+	 *
170
+	 * @param string $uri
171
+	 *
172
+	 * @return string
173
+	 */
174
+	public function findByUri($uri)
175
+	{
176
+		return $this->principalBackend->findByUri($uri, $this->principalPrefix);
177
+	}
178 178
 }
Please login to merge, or discard this patch.
sabre/sabre/dav/lib/DAVACL/Xml/Request/AclPrincipalPropSetReport.php 1 patch
Indentation   +36 added lines, -36 removed lines patch added patch discarded remove patch
@@ -21,46 +21,46 @@
 block discarded – undo
21 21
  */
22 22
 class AclPrincipalPropSetReport implements XmlDeserializable
23 23
 {
24
-    public $properties = [];
24
+	public $properties = [];
25 25
 
26
-    /**
27
-     * The deserialize method is called during xml parsing.
28
-     *
29
-     * This method is called statically, this is because in theory this method
30
-     * may be used as a type of constructor, or factory method.
31
-     *
32
-     * Often you want to return an instance of the current class, but you are
33
-     * free to return other data as well.
34
-     *
35
-     * You are responsible for advancing the reader to the next element. Not
36
-     * doing anything will result in a never-ending loop.
37
-     *
38
-     * If you just want to skip parsing for this element altogether, you can
39
-     * just call $reader->next();
40
-     *
41
-     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
42
-     * the next element.
43
-     *
44
-     * @return mixed
45
-     */
46
-    public static function xmlDeserialize(Reader $reader)
47
-    {
48
-        $reader->pushContext();
49
-        $reader->elementMap['{DAV:}prop'] = 'Sabre\Xml\Deserializer\enum';
26
+	/**
27
+	 * The deserialize method is called during xml parsing.
28
+	 *
29
+	 * This method is called statically, this is because in theory this method
30
+	 * may be used as a type of constructor, or factory method.
31
+	 *
32
+	 * Often you want to return an instance of the current class, but you are
33
+	 * free to return other data as well.
34
+	 *
35
+	 * You are responsible for advancing the reader to the next element. Not
36
+	 * doing anything will result in a never-ending loop.
37
+	 *
38
+	 * If you just want to skip parsing for this element altogether, you can
39
+	 * just call $reader->next();
40
+	 *
41
+	 * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
42
+	 * the next element.
43
+	 *
44
+	 * @return mixed
45
+	 */
46
+	public static function xmlDeserialize(Reader $reader)
47
+	{
48
+		$reader->pushContext();
49
+		$reader->elementMap['{DAV:}prop'] = 'Sabre\Xml\Deserializer\enum';
50 50
 
51
-        $elems = Deserializer\keyValue(
52
-            $reader,
53
-            'DAV:'
54
-        );
51
+		$elems = Deserializer\keyValue(
52
+			$reader,
53
+			'DAV:'
54
+		);
55 55
 
56
-        $reader->popContext();
56
+		$reader->popContext();
57 57
 
58
-        $report = new self();
58
+		$report = new self();
59 59
 
60
-        if (!empty($elems['prop'])) {
61
-            $report->properties = $elems['prop'];
62
-        }
60
+		if (!empty($elems['prop'])) {
61
+			$report->properties = $elems['prop'];
62
+		}
63 63
 
64
-        return $report;
65
-    }
64
+		return $report;
65
+	}
66 66
 }
Please login to merge, or discard this patch.
includes/sabre/sabre/dav/lib/DAVACL/Xml/Request/PrincipalMatchReport.php 1 patch
Indentation   +81 added lines, -81 removed lines patch added patch discarded remove patch
@@ -22,85 +22,85 @@
 block discarded – undo
22 22
  */
23 23
 class PrincipalMatchReport implements XmlDeserializable
24 24
 {
25
-    /**
26
-     * Report on a list of principals that match the current principal.
27
-     */
28
-    const SELF = 1;
29
-
30
-    /**
31
-     * Report on a property on resources, such as {DAV:}owner, that match the current principal.
32
-     */
33
-    const PRINCIPAL_PROPERTY = 2;
34
-
35
-    /**
36
-     * Must be SELF or PRINCIPAL_PROPERTY.
37
-     *
38
-     * @var int
39
-     */
40
-    public $type;
41
-
42
-    /**
43
-     * List of properties that are being requested for matching resources.
44
-     *
45
-     * @var string[]
46
-     */
47
-    public $properties = [];
48
-
49
-    /**
50
-     * If $type = PRINCIPAL_PROPERTY, which WebDAV property we should compare
51
-     * to the current principal.
52
-     *
53
-     * @var string
54
-     */
55
-    public $principalProperty;
56
-
57
-    /**
58
-     * The deserialize method is called during xml parsing.
59
-     *
60
-     * This method is called statically, this is because in theory this method
61
-     * may be used as a type of constructor, or factory method.
62
-     *
63
-     * Often you want to return an instance of the current class, but you are
64
-     * free to return other data as well.
65
-     *
66
-     * You are responsible for advancing the reader to the next element. Not
67
-     * doing anything will result in a never-ending loop.
68
-     *
69
-     * If you just want to skip parsing for this element altogether, you can
70
-     * just call $reader->next();
71
-     *
72
-     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
73
-     * the next element.
74
-     *
75
-     * @return mixed
76
-     */
77
-    public static function xmlDeserialize(Reader $reader)
78
-    {
79
-        $reader->pushContext();
80
-        $reader->elementMap['{DAV:}prop'] = 'Sabre\Xml\Deserializer\enum';
81
-
82
-        $elems = Deserializer\keyValue(
83
-            $reader,
84
-            'DAV:'
85
-        );
86
-
87
-        $reader->popContext();
88
-
89
-        $principalMatch = new self();
90
-
91
-        if (array_key_exists('self', $elems)) {
92
-            $principalMatch->type = self::SELF;
93
-        }
94
-
95
-        if (array_key_exists('principal-property', $elems)) {
96
-            $principalMatch->type = self::PRINCIPAL_PROPERTY;
97
-            $principalMatch->principalProperty = $elems['principal-property'][0]['name'];
98
-        }
99
-
100
-        if (!empty($elems['prop'])) {
101
-            $principalMatch->properties = $elems['prop'];
102
-        }
103
-
104
-        return $principalMatch;
105
-    }
25
+	/**
26
+	 * Report on a list of principals that match the current principal.
27
+	 */
28
+	const SELF = 1;
29
+
30
+	/**
31
+	 * Report on a property on resources, such as {DAV:}owner, that match the current principal.
32
+	 */
33
+	const PRINCIPAL_PROPERTY = 2;
34
+
35
+	/**
36
+	 * Must be SELF or PRINCIPAL_PROPERTY.
37
+	 *
38
+	 * @var int
39
+	 */
40
+	public $type;
41
+
42
+	/**
43
+	 * List of properties that are being requested for matching resources.
44
+	 *
45
+	 * @var string[]
46
+	 */
47
+	public $properties = [];
48
+
49
+	/**
50
+	 * If $type = PRINCIPAL_PROPERTY, which WebDAV property we should compare
51
+	 * to the current principal.
52
+	 *
53
+	 * @var string
54
+	 */
55
+	public $principalProperty;
56
+
57
+	/**
58
+	 * The deserialize method is called during xml parsing.
59
+	 *
60
+	 * This method is called statically, this is because in theory this method
61
+	 * may be used as a type of constructor, or factory method.
62
+	 *
63
+	 * Often you want to return an instance of the current class, but you are
64
+	 * free to return other data as well.
65
+	 *
66
+	 * You are responsible for advancing the reader to the next element. Not
67
+	 * doing anything will result in a never-ending loop.
68
+	 *
69
+	 * If you just want to skip parsing for this element altogether, you can
70
+	 * just call $reader->next();
71
+	 *
72
+	 * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
73
+	 * the next element.
74
+	 *
75
+	 * @return mixed
76
+	 */
77
+	public static function xmlDeserialize(Reader $reader)
78
+	{
79
+		$reader->pushContext();
80
+		$reader->elementMap['{DAV:}prop'] = 'Sabre\Xml\Deserializer\enum';
81
+
82
+		$elems = Deserializer\keyValue(
83
+			$reader,
84
+			'DAV:'
85
+		);
86
+
87
+		$reader->popContext();
88
+
89
+		$principalMatch = new self();
90
+
91
+		if (array_key_exists('self', $elems)) {
92
+			$principalMatch->type = self::SELF;
93
+		}
94
+
95
+		if (array_key_exists('principal-property', $elems)) {
96
+			$principalMatch->type = self::PRINCIPAL_PROPERTY;
97
+			$principalMatch->principalProperty = $elems['principal-property'][0]['name'];
98
+		}
99
+
100
+		if (!empty($elems['prop'])) {
101
+			$principalMatch->properties = $elems['prop'];
102
+		}
103
+
104
+		return $principalMatch;
105
+	}
106 106
 }
Please login to merge, or discard this patch.
sabre/sabre/dav/lib/DAVACL/Xml/Request/PrincipalSearchPropertySetReport.php 1 patch
Indentation   +30 added lines, -30 removed lines patch added patch discarded remove patch
@@ -22,37 +22,37 @@
 block discarded – undo
22 22
  */
23 23
 class PrincipalSearchPropertySetReport implements XmlDeserializable
24 24
 {
25
-    /**
26
-     * The deserialize method is called during xml parsing.
27
-     *
28
-     * This method is called statically, this is because in theory this method
29
-     * may be used as a type of constructor, or factory method.
30
-     *
31
-     * Often you want to return an instance of the current class, but you are
32
-     * free to return other data as well.
33
-     *
34
-     * You are responsible for advancing the reader to the next element. Not
35
-     * doing anything will result in a never-ending loop.
36
-     *
37
-     * If you just want to skip parsing for this element altogether, you can
38
-     * just call $reader->next();
39
-     *
40
-     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
41
-     * the next element.
42
-     *
43
-     * @return mixed
44
-     */
45
-    public static function xmlDeserialize(Reader $reader)
46
-    {
47
-        if (!$reader->isEmptyElement) {
48
-            throw new BadRequest('The {DAV:}principal-search-property-set element must be empty');
49
-        }
25
+	/**
26
+	 * The deserialize method is called during xml parsing.
27
+	 *
28
+	 * This method is called statically, this is because in theory this method
29
+	 * may be used as a type of constructor, or factory method.
30
+	 *
31
+	 * Often you want to return an instance of the current class, but you are
32
+	 * free to return other data as well.
33
+	 *
34
+	 * You are responsible for advancing the reader to the next element. Not
35
+	 * doing anything will result in a never-ending loop.
36
+	 *
37
+	 * If you just want to skip parsing for this element altogether, you can
38
+	 * just call $reader->next();
39
+	 *
40
+	 * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
41
+	 * the next element.
42
+	 *
43
+	 * @return mixed
44
+	 */
45
+	public static function xmlDeserialize(Reader $reader)
46
+	{
47
+		if (!$reader->isEmptyElement) {
48
+			throw new BadRequest('The {DAV:}principal-search-property-set element must be empty');
49
+		}
50 50
 
51
-        // The element is actually empty, so there's not much to do.
52
-        $reader->next();
51
+		// The element is actually empty, so there's not much to do.
52
+		$reader->next();
53 53
 
54
-        $self = new self();
54
+		$self = new self();
55 55
 
56
-        return $self;
57
-    }
56
+		return $self;
57
+	}
58 58
 }
Please login to merge, or discard this patch.
includes/sabre/sabre/dav/lib/DAVACL/Xml/Request/ExpandPropertyReport.php 2 patches
Indentation   +67 added lines, -67 removed lines patch added patch discarded remove patch
@@ -20,81 +20,81 @@
 block discarded – undo
20 20
  */
21 21
 class ExpandPropertyReport implements XmlDeserializable
22 22
 {
23
-    /**
24
-     * An array with requested properties.
25
-     *
26
-     * The requested properties will be used as keys in this array. The value
27
-     * is normally null.
28
-     *
29
-     * If the value is an array though, it means the property must be expanded.
30
-     * Within the array, the sub-properties, which themselves may be null or
31
-     * arrays.
32
-     *
33
-     * @var array
34
-     */
35
-    public $properties;
23
+	/**
24
+	 * An array with requested properties.
25
+	 *
26
+	 * The requested properties will be used as keys in this array. The value
27
+	 * is normally null.
28
+	 *
29
+	 * If the value is an array though, it means the property must be expanded.
30
+	 * Within the array, the sub-properties, which themselves may be null or
31
+	 * arrays.
32
+	 *
33
+	 * @var array
34
+	 */
35
+	public $properties;
36 36
 
37
-    /**
38
-     * The deserialize method is called during xml parsing.
39
-     *
40
-     * This method is called statically, this is because in theory this method
41
-     * may be used as a type of constructor, or factory method.
42
-     *
43
-     * Often you want to return an instance of the current class, but you are
44
-     * free to return other data as well.
45
-     *
46
-     * You are responsible for advancing the reader to the next element. Not
47
-     * doing anything will result in a never-ending loop.
48
-     *
49
-     * If you just want to skip parsing for this element altogether, you can
50
-     * just call $reader->next();
51
-     *
52
-     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
53
-     * the next element.
54
-     *
55
-     * @return mixed
56
-     */
57
-    public static function xmlDeserialize(Reader $reader)
58
-    {
59
-        $elems = $reader->parseInnerTree();
37
+	/**
38
+	 * The deserialize method is called during xml parsing.
39
+	 *
40
+	 * This method is called statically, this is because in theory this method
41
+	 * may be used as a type of constructor, or factory method.
42
+	 *
43
+	 * Often you want to return an instance of the current class, but you are
44
+	 * free to return other data as well.
45
+	 *
46
+	 * You are responsible for advancing the reader to the next element. Not
47
+	 * doing anything will result in a never-ending loop.
48
+	 *
49
+	 * If you just want to skip parsing for this element altogether, you can
50
+	 * just call $reader->next();
51
+	 *
52
+	 * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
53
+	 * the next element.
54
+	 *
55
+	 * @return mixed
56
+	 */
57
+	public static function xmlDeserialize(Reader $reader)
58
+	{
59
+		$elems = $reader->parseInnerTree();
60 60
 
61
-        $obj = new self();
62
-        $obj->properties = self::traverse($elems);
61
+		$obj = new self();
62
+		$obj->properties = self::traverse($elems);
63 63
 
64
-        return $obj;
65
-    }
64
+		return $obj;
65
+	}
66 66
 
67
-    /**
68
-     * This method is used by deserializeXml, to recursively parse the
69
-     * {DAV:}property elements.
70
-     *
71
-     * @param array $elems
72
-     *
73
-     * @return array
74
-     */
75
-    private static function traverse($elems)
76
-    {
77
-        $result = [];
67
+	/**
68
+	 * This method is used by deserializeXml, to recursively parse the
69
+	 * {DAV:}property elements.
70
+	 *
71
+	 * @param array $elems
72
+	 *
73
+	 * @return array
74
+	 */
75
+	private static function traverse($elems)
76
+	{
77
+		$result = [];
78 78
 
79
-        foreach ($elems as $elem) {
80
-            if ('{DAV:}property' !== $elem['name']) {
81
-                continue;
82
-            }
79
+		foreach ($elems as $elem) {
80
+			if ('{DAV:}property' !== $elem['name']) {
81
+				continue;
82
+			}
83 83
 
84
-            $namespace = isset($elem['attributes']['namespace']) ?
85
-                $elem['attributes']['namespace'] :
86
-                'DAV:';
84
+			$namespace = isset($elem['attributes']['namespace']) ?
85
+				$elem['attributes']['namespace'] :
86
+				'DAV:';
87 87
 
88
-            $propName = '{'.$namespace.'}'.$elem['attributes']['name'];
88
+			$propName = '{'.$namespace.'}'.$elem['attributes']['name'];
89 89
 
90
-            $value = null;
91
-            if (is_array($elem['value'])) {
92
-                $value = self::traverse($elem['value']);
93
-            }
90
+			$value = null;
91
+			if (is_array($elem['value'])) {
92
+				$value = self::traverse($elem['value']);
93
+			}
94 94
 
95
-            $result[$propName] = $value;
96
-        }
95
+			$result[$propName] = $value;
96
+		}
97 97
 
98
-        return $result;
99
-    }
98
+		return $result;
99
+	}
100 100
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -2 removed lines patch added patch discarded remove patch
@@ -82,8 +82,7 @@
 block discarded – undo
82 82
             }
83 83
 
84 84
             $namespace = isset($elem['attributes']['namespace']) ?
85
-                $elem['attributes']['namespace'] :
86
-                'DAV:';
85
+                $elem['attributes']['namespace'] : 'DAV:';
87 86
 
88 87
             $propName = '{'.$namespace.'}'.$elem['attributes']['name'];
89 88
 
Please login to merge, or discard this patch.