Completed
Push — developer ( d751a3...e1a3df )
by Błażej
164:44 queued 111:37
created
libraries/SabreDAV/HTTP/Auth/AbstractAuth.php 1 patch
Indentation   +44 added lines, -44 removed lines patch added patch discarded remove patch
@@ -16,58 +16,58 @@
 block discarded – undo
16 16
  */
17 17
 abstract class AbstractAuth {
18 18
 
19
-    /**
20
-     * Authentication realm
21
-     *
22
-     * @var string
23
-     */
24
-    protected $realm;
19
+	/**
20
+	 * Authentication realm
21
+	 *
22
+	 * @var string
23
+	 */
24
+	protected $realm;
25 25
 
26
-    /**
27
-     * Request object
28
-     *
29
-     * @var RequestInterface
30
-     */
31
-    protected $request;
26
+	/**
27
+	 * Request object
28
+	 *
29
+	 * @var RequestInterface
30
+	 */
31
+	protected $request;
32 32
 
33
-    /**
34
-     * Response object
35
-     *
36
-     * @var ResponseInterface
37
-     */
38
-    protected $response;
33
+	/**
34
+	 * Response object
35
+	 *
36
+	 * @var ResponseInterface
37
+	 */
38
+	protected $response;
39 39
 
40
-    /**
41
-     * Creates the object
42
-     *
43
-     * @param string $realm
44
-     * @return void
45
-     */
46
-    public function __construct($realm = 'SabreTooth', RequestInterface $request, ResponseInterface $response) {
40
+	/**
41
+	 * Creates the object
42
+	 *
43
+	 * @param string $realm
44
+	 * @return void
45
+	 */
46
+	public function __construct($realm = 'SabreTooth', RequestInterface $request, ResponseInterface $response) {
47 47
 
48
-        $this->realm = $realm;
49
-        $this->request = $request;
50
-        $this->response = $response;
48
+		$this->realm = $realm;
49
+		$this->request = $request;
50
+		$this->response = $response;
51 51
 
52
-    }
52
+	}
53 53
 
54
-    /**
55
-     * This method sends the needed HTTP header and statuscode (401) to force
56
-     * the user to login.
57
-     *
58
-     * @return void
59
-     */
60
-    abstract function requireLogin();
54
+	/**
55
+	 * This method sends the needed HTTP header and statuscode (401) to force
56
+	 * the user to login.
57
+	 *
58
+	 * @return void
59
+	 */
60
+	abstract function requireLogin();
61 61
 
62
-    /**
63
-     * Returns the HTTP realm
64
-     *
65
-     * @return string
66
-     */
67
-    public function getRealm() {
62
+	/**
63
+	 * Returns the HTTP realm
64
+	 *
65
+	 * @return string
66
+	 */
67
+	public function getRealm() {
68 68
 
69
-        return $this->realm;
69
+		return $this->realm;
70 70
 
71
-    }
71
+	}
72 72
 
73 73
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/VObject/Component.php 1 patch
Indentation   +680 added lines, -680 removed lines patch added patch discarded remove patch
@@ -16,685 +16,685 @@
 block discarded – undo
16 16
  */
17 17
 class Component extends Node {
18 18
 
19
-    /**
20
-     * Component name.
21
-     *
22
-     * This will contain a string such as VEVENT, VTODO, VCALENDAR, VCARD.
23
-     *
24
-     * @var string
25
-     */
26
-    public $name;
27
-
28
-    /**
29
-     * A list of properties and/or sub-components.
30
-     *
31
-     * @var array
32
-     */
33
-    protected $children = [];
34
-
35
-    /**
36
-     * Creates a new component.
37
-     *
38
-     * You can specify the children either in key=>value syntax, in which case
39
-     * properties will automatically be created, or you can just pass a list of
40
-     * Component and Property object.
41
-     *
42
-     * By default, a set of sensible values will be added to the component. For
43
-     * an iCalendar object, this may be something like CALSCALE:GREGORIAN. To
44
-     * ensure that this does not happen, set $defaults to false.
45
-     *
46
-     * @param Document $root
47
-     * @param string $name such as VCALENDAR, VEVENT.
48
-     * @param array $children
49
-     * @param bool $defaults
50
-     *
51
-     * @return void
52
-     */
53
-    public function __construct(Document $root, $name, array $children = [], $defaults = true) {
54
-
55
-        $this->name = strtoupper($name);
56
-        $this->root = $root;
57
-
58
-        if ($defaults) {
59
-            // This is a terribly convoluted way to do this, but this ensures
60
-            // that the order of properties as they are specified in both
61
-            // defaults and the childrens list, are inserted in the object in a
62
-            // natural way.
63
-            $list = $this->getDefaults();
64
-            $nodes = [];
65
-            foreach ($children as $key => $value) {
66
-                if ($value instanceof Node) {
67
-                    if (isset($list[$value->name])) {
68
-                        unset($list[$value->name]);
69
-                    }
70
-                    $nodes[] = $value;
71
-                } else {
72
-                    $list[$key] = $value;
73
-                }
74
-            }
75
-            foreach ($list as $key => $value) {
76
-                $this->add($key, $value);
77
-            }
78
-            foreach ($nodes as $node) {
79
-                $this->add($node);
80
-            }
81
-        } else {
82
-            foreach ($children as $k => $child) {
83
-                if ($child instanceof Node) {
84
-                    // Component or Property
85
-                    $this->add($child);
86
-                } else {
87
-
88
-                    // Property key=>value
89
-                    $this->add($k, $child);
90
-                }
91
-            }
92
-        }
93
-
94
-    }
95
-
96
-    /**
97
-     * Adds a new property or component, and returns the new item.
98
-     *
99
-     * This method has 3 possible signatures:
100
-     *
101
-     * add(Component $comp) // Adds a new component
102
-     * add(Property $prop)  // Adds a new property
103
-     * add($name, $value, array $parameters = []) // Adds a new property
104
-     * add($name, array $children = []) // Adds a new component
105
-     * by name.
106
-     *
107
-     * @return Node
108
-     */
109
-    public function add() {
110
-
111
-        $arguments = func_get_args();
112
-
113
-        if ($arguments[0] instanceof Node) {
114
-            if (isset($arguments[1])) {
115
-                throw new \InvalidArgumentException('The second argument must not be specified, when passing a VObject Node');
116
-            }
117
-            $arguments[0]->parent = $this;
118
-            $newNode = $arguments[0];
119
-
120
-        } elseif (is_string($arguments[0])) {
121
-
122
-            $newNode = call_user_func_array([$this->root, 'create'], $arguments);
123
-
124
-        } else {
125
-
126
-            throw new \InvalidArgumentException('The first argument must either be a \\Sabre\\VObject\\Node or a string');
127
-
128
-        }
129
-
130
-        $name = $newNode->name;
131
-        if (isset($this->children[$name])) {
132
-            $this->children[$name][] = $newNode;
133
-        } else {
134
-            $this->children[$name] = [$newNode];
135
-        }
136
-        return $newNode;
137
-
138
-    }
139
-
140
-    /**
141
-     * This method removes a component or property from this component.
142
-     *
143
-     * You can either specify the item by name (like DTSTART), in which case
144
-     * all properties/components with that name will be removed, or you can
145
-     * pass an instance of a property or component, in which case only that
146
-     * exact item will be removed.
147
-     *
148
-     * @param string|Property|Component $item
149
-     * @return void
150
-     */
151
-    public function remove($item) {
152
-
153
-        if (is_string($item)) {
154
-            // If there's no dot in the name, it's an exact property name and
155
-            // we can just wipe out all those properties.
156
-            //
157
-            if (strpos($item, '.') === false) {
158
-                unset($this->children[strtoupper($item)]);
159
-                return;
160
-            }
161
-            // If there was a dot, we need to ask select() to help us out and
162
-            // then we just call remove recursively.
163
-            foreach ($this->select($item) as $child) {
164
-
165
-                $this->remove($child);
166
-
167
-            }
168
-        } else {
169
-            foreach ($this->select($item->name) as $k => $child) {
170
-                if ($child === $item) {
171
-                    unset($this->children[$item->name][$k]);
172
-                    return;
173
-                }
174
-            }
175
-        }
176
-
177
-        throw new \InvalidArgumentException('The item you passed to remove() was not a child of this component');
178
-
179
-    }
180
-
181
-    /**
182
-     * Returns a flat list of all the properties and components in this
183
-     * component.
184
-     *
185
-     * @return array
186
-     */
187
-    public function children() {
188
-
189
-        $result = [];
190
-        foreach ($this->children as $childGroup) {
191
-            $result = array_merge($result, $childGroup);
192
-        }
193
-        return $result;
194
-
195
-    }
196
-
197
-    /**
198
-     * This method only returns a list of sub-components. Properties are
199
-     * ignored.
200
-     *
201
-     * @return array
202
-     */
203
-    public function getComponents() {
204
-
205
-        $result = [];
206
-
207
-        foreach ($this->children as $childGroup) {
208
-            foreach ($childGroup as $child) {
209
-                if ($child instanceof self) {
210
-                    $result[] = $child;
211
-                }
212
-            }
213
-        }
214
-        return $result;
215
-
216
-    }
217
-
218
-    /**
219
-     * Returns an array with elements that match the specified name.
220
-     *
221
-     * This function is also aware of MIME-Directory groups (as they appear in
222
-     * vcards). This means that if a property is grouped as "HOME.EMAIL", it
223
-     * will also be returned when searching for just "EMAIL". If you want to
224
-     * search for a property in a specific group, you can select on the entire
225
-     * string ("HOME.EMAIL"). If you want to search on a specific property that
226
-     * has not been assigned a group, specify ".EMAIL".
227
-     *
228
-     * @param string $name
229
-     * @return array
230
-     */
231
-    public function select($name) {
232
-
233
-        $group = null;
234
-        $name = strtoupper($name);
235
-        if (strpos($name, '.') !== false) {
236
-            list($group, $name) = explode('.', $name, 2);
237
-        }
238
-        if ($name === '') $name = null;
239
-
240
-        if (!is_null($name)) {
241
-
242
-            $result = isset($this->children[$name]) ? $this->children[$name] : [];
243
-
244
-            if (is_null($group)) {
245
-                return $result;
246
-            } else {
247
-                // If we have a group filter as well, we need to narrow it down
248
-                // more.
249
-                return array_filter(
250
-                    $result,
251
-                    public function($child) use ($group) {
252
-
253
-                        return $child instanceof Property && strtoupper($child->group) === $group;
254
-
255
-                    }
256
-                );
257
-            }
258
-
259
-        }
260
-
261
-        // If we got to this point, it means there was no 'name' specified for
262
-        // searching, implying that this is a group-only search.
263
-        $result = [];
264
-        foreach ($this->children as $childGroup) {
265
-
266
-            foreach ($childGroup as $child) {
267
-
268
-                if ($child instanceof Property && strtoupper($child->group) === $group) {
269
-                    $result[] = $child;
270
-                }
271
-
272
-            }
273
-
274
-        }
275
-        return $result;
276
-
277
-    }
278
-
279
-    /**
280
-     * Turns the object back into a serialized blob.
281
-     *
282
-     * @return string
283
-     */
284
-    public function serialize() {
285
-
286
-        $str = "BEGIN:" . $this->name . "\r\n";
287
-
288
-        /**
289
-         * Gives a component a 'score' for sorting purposes.
290
-         *
291
-         * This is solely used by the childrenSort method.
292
-         *
293
-         * A higher score means the item will be lower in the list.
294
-         * To avoid score collisions, each "score category" has a reasonable
295
-         * space to accomodate elements. The $key is added to the $score to
296
-         * preserve the original relative order of elements.
297
-         *
298
-         * @param int $key
299
-         * @param array $array
300
-         *
301
-         * @return int
302
-         */
303
-        $sortScore = function($key, $array) {
304
-
305
-            if ($array[$key] instanceof Component) {
306
-
307
-                // We want to encode VTIMEZONE first, this is a personal
308
-                // preference.
309
-                if ($array[$key]->name === 'VTIMEZONE') {
310
-                    $score = 300000000;
311
-                    return $score + $key;
312
-                } else {
313
-                    $score = 400000000;
314
-                    return $score + $key;
315
-                }
316
-            } else {
317
-                // Properties get encoded first
318
-                // VCARD version 4.0 wants the VERSION property to appear first
319
-                if ($array[$key] instanceof Property) {
320
-                    if ($array[$key]->name === 'VERSION') {
321
-                        $score = 100000000;
322
-                        return $score + $key;
323
-                    } else {
324
-                        // All other properties
325
-                        $score = 200000000;
326
-                        return $score + $key;
327
-                    }
328
-                }
329
-            }
330
-
331
-        };
332
-
333
-        $children = $this->children();
334
-        $tmp = $children;
335
-        uksort(
336
-            $children,
337
-            public function($a, $b) use ($sortScore, $tmp) {
338
-
339
-                $sA = $sortScore($a, $tmp);
340
-                $sB = $sortScore($b, $tmp);
341
-
342
-                return $sA - $sB;
343
-
344
-            }
345
-        );
346
-
347
-        foreach ($children as $child) $str .= $child->serialize();
348
-        $str .= "END:" . $this->name . "\r\n";
349
-
350
-        return $str;
351
-
352
-    }
353
-
354
-    /**
355
-     * This method returns an array, with the representation as it should be
356
-     * encoded in JSON. This is used to create jCard or jCal documents.
357
-     *
358
-     * @return array
359
-     */
360
-    public function jsonSerialize() {
361
-
362
-        $components = [];
363
-        $properties = [];
364
-
365
-        foreach ($this->children as $childGroup) {
366
-            foreach ($childGroup as $child) {
367
-                if ($child instanceof self) {
368
-                    $components[] = $child->jsonSerialize();
369
-                } else {
370
-                    $properties[] = $child->jsonSerialize();
371
-                }
372
-            }
373
-        }
374
-
375
-        return [
376
-            strtolower($this->name),
377
-            $properties,
378
-            $components
379
-        ];
380
-
381
-    }
382
-
383
-    /**
384
-     * This method serializes the data into XML. This is used to create xCard or
385
-     * xCal documents.
386
-     *
387
-     * @param Xml\Writer $writer  XML writer.
388
-     *
389
-     * @return void
390
-     */
391
-    public function xmlSerialize(Xml\Writer $writer) {
392
-
393
-        $components = [];
394
-        $properties = [];
395
-
396
-        foreach ($this->children as $childGroup) {
397
-            foreach ($childGroup as $child) {
398
-                if ($child instanceof self) {
399
-                    $components[] = $child;
400
-                } else {
401
-                    $properties[] = $child;
402
-                }
403
-            }
404
-        }
405
-
406
-        $writer->startElement(strtolower($this->name));
407
-
408
-        if (!empty($properties)) {
409
-
410
-            $writer->startElement('properties');
411
-
412
-            foreach ($properties as $property) {
413
-                $property->xmlSerialize($writer);
414
-            }
415
-
416
-            $writer->endElement();
417
-
418
-        }
419
-
420
-        if (!empty($components)) {
421
-
422
-            $writer->startElement('components');
423
-
424
-            foreach ($components as $component) {
425
-                $component->xmlSerialize($writer);
426
-            }
427
-
428
-            $writer->endElement();
429
-        }
430
-
431
-        $writer->endElement();
432
-
433
-    }
434
-
435
-    /**
436
-     * This method should return a list of default property values.
437
-     *
438
-     * @return array
439
-     */
440
-    protected function getDefaults() {
441
-
442
-        return [];
443
-
444
-    }
445
-
446
-    /* Magic property accessors {{{ */
447
-
448
-    /**
449
-     * Using 'get' you will either get a property or component.
450
-     *
451
-     * If there were no child-elements found with the specified name,
452
-     * null is returned.
453
-     *
454
-     * To use this, this may look something like this:
455
-     *
456
-     * $event = $calendar->VEVENT;
457
-     *
458
-     * @param string $name
459
-     *
460
-     * @return Property
461
-     */
462
-    public function __get($name) {
463
-
464
-        if ($name === 'children') {
465
-
466
-            throw new \RuntimeException('Starting sabre/vobject 4.0 the children property is now protected. You should use the children() method instead');
467
-
468
-        }
469
-
470
-        $matches = $this->select($name);
471
-        if (count($matches) === 0) {
472
-            return;
473
-        } else {
474
-            $firstMatch = current($matches);
475
-            /** @var $firstMatch Property */
476
-            $firstMatch->setIterator(new ElementList(array_values($matches)));
477
-            return $firstMatch;
478
-        }
479
-
480
-    }
481
-
482
-    /**
483
-     * This method checks if a sub-element with the specified name exists.
484
-     *
485
-     * @param string $name
486
-     *
487
-     * @return bool
488
-     */
489
-    public function __isset($name) {
490
-
491
-        $matches = $this->select($name);
492
-        return count($matches) > 0;
493
-
494
-    }
495
-
496
-    /**
497
-     * Using the setter method you can add properties or subcomponents.
498
-     *
499
-     * You can either pass a Component, Property
500
-     * object, or a string to automatically create a Property.
501
-     *
502
-     * If the item already exists, it will be removed. If you want to add
503
-     * a new item with the same name, always use the add() method.
504
-     *
505
-     * @param string $name
506
-     * @param mixed $value
507
-     *
508
-     * @return void
509
-     */
510
-    public function __set($name, $value) {
511
-
512
-        $name = strtoupper($name);
513
-        $this->remove($name);
514
-        if ($value instanceof self || $value instanceof Property) {
515
-            $this->add($value);
516
-        } else {
517
-            $this->add($name, $value);
518
-        }
519
-    }
520
-
521
-    /**
522
-     * Removes all properties and components within this component with the
523
-     * specified name.
524
-     *
525
-     * @param string $name
526
-     *
527
-     * @return void
528
-     */
529
-    public function __unset($name) {
530
-
531
-        $this->remove($name);
532
-
533
-    }
534
-
535
-    /* }}} */
536
-
537
-    /**
538
-     * This method is automatically called when the object is cloned.
539
-     * Specifically, this will ensure all child elements are also cloned.
540
-     *
541
-     * @return void
542
-     */
543
-    public function __clone() {
544
-
545
-        foreach ($this->children as $childName => $childGroup) {
546
-            foreach ($childGroup as $key => $child) {
547
-                $clonedChild = clone $child;
548
-                $clonedChild->parent = $this;
549
-                $clonedChild->root = $this->root;
550
-                $this->children[$childName][$key] = $clonedChild;
551
-            }
552
-        }
553
-
554
-    }
555
-
556
-    /**
557
-     * A simple list of validation rules.
558
-     *
559
-     * This is simply a list of properties, and how many times they either
560
-     * must or must not appear.
561
-     *
562
-     * Possible values per property:
563
-     *   * 0 - Must not appear.
564
-     *   * 1 - Must appear exactly once.
565
-     *   * + - Must appear at least once.
566
-     *   * * - Can appear any number of times.
567
-     *   * ? - May appear, but not more than once.
568
-     *
569
-     * It is also possible to specify defaults and severity levels for
570
-     * violating the rule.
571
-     *
572
-     * See the VEVENT implementation for getValidationRules for a more complex
573
-     * example.
574
-     *
575
-     * @var array
576
-     */
577
-    public function getValidationRules() {
578
-
579
-        return [];
580
-
581
-    }
582
-
583
-    /**
584
-     * Validates the node for correctness.
585
-     *
586
-     * The following options are supported:
587
-     *   Node::REPAIR - May attempt to automatically repair the problem.
588
-     *   Node::PROFILE_CARDDAV - Validate the vCard for CardDAV purposes.
589
-     *   Node::PROFILE_CALDAV - Validate the iCalendar for CalDAV purposes.
590
-     *
591
-     * This method returns an array with detected problems.
592
-     * Every element has the following properties:
593
-     *
594
-     *  * level - problem level.
595
-     *  * message - A human-readable string describing the issue.
596
-     *  * node - A reference to the problematic node.
597
-     *
598
-     * The level means:
599
-     *   1 - The issue was repaired (only happens if REPAIR was turned on).
600
-     *   2 - A warning.
601
-     *   3 - An error.
602
-     *
603
-     * @param int $options
604
-     *
605
-     * @return array
606
-     */
607
-    public function validate($options = 0) {
608
-
609
-        $rules = $this->getValidationRules();
610
-        $defaults = $this->getDefaults();
611
-
612
-        $propertyCounters = [];
613
-
614
-        $messages = [];
615
-
616
-        foreach ($this->children() as $child) {
617
-            $name = strtoupper($child->name);
618
-            if (!isset($propertyCounters[$name])) {
619
-                $propertyCounters[$name] = 1;
620
-            } else {
621
-                $propertyCounters[$name]++;
622
-            }
623
-            $messages = array_merge($messages, $child->validate($options));
624
-        }
625
-
626
-        foreach ($rules as $propName => $rule) {
627
-
628
-            switch ($rule) {
629
-                case '0' :
630
-                    if (isset($propertyCounters[$propName])) {
631
-                        $messages[] = [
632
-                            'level'   => 3,
633
-                            'message' => $propName . ' MUST NOT appear in a ' . $this->name . ' component',
634
-                            'node'    => $this,
635
-                        ];
636
-                    }
637
-                    break;
638
-                case '1' :
639
-                    if (!isset($propertyCounters[$propName]) || $propertyCounters[$propName] !== 1) {
640
-                        $repaired = false;
641
-                        if ($options & self::REPAIR && isset($defaults[$propName])) {
642
-                            $this->add($propName, $defaults[$propName]);
643
-                            $repaired = true;
644
-                        }
645
-                        $messages[] = [
646
-                            'level'   => $repaired ? 1 : 3,
647
-                            'message' => $propName . ' MUST appear exactly once in a ' . $this->name . ' component',
648
-                            'node'    => $this,
649
-                        ];
650
-                    }
651
-                    break;
652
-                case '+' :
653
-                    if (!isset($propertyCounters[$propName]) || $propertyCounters[$propName] < 1) {
654
-                        $messages[] = [
655
-                            'level'   => 3,
656
-                            'message' => $propName . ' MUST appear at least once in a ' . $this->name . ' component',
657
-                            'node'    => $this,
658
-                        ];
659
-                    }
660
-                    break;
661
-                case '*' :
662
-                    break;
663
-                case '?' :
664
-                    if (isset($propertyCounters[$propName]) && $propertyCounters[$propName] > 1) {
665
-                        $messages[] = [
666
-                            'level'   => 3,
667
-                            'message' => $propName . ' MUST NOT appear more than once in a ' . $this->name . ' component',
668
-                            'node'    => $this,
669
-                        ];
670
-                    }
671
-                    break;
672
-
673
-            }
674
-
675
-        }
676
-        return $messages;
677
-
678
-    }
679
-
680
-    /**
681
-     * Call this method on a document if you're done using it.
682
-     *
683
-     * It's intended to remove all circular references, so PHP can easily clean
684
-     * it up.
685
-     *
686
-     * @return void
687
-     */
688
-    public function destroy() {
689
-
690
-        parent::destroy();
691
-        foreach ($this->children as $childGroup) {
692
-            foreach ($childGroup as $child) {
693
-                $child->destroy();
694
-            }
695
-        }
696
-        $this->children = [];
697
-
698
-    }
19
+	/**
20
+	 * Component name.
21
+	 *
22
+	 * This will contain a string such as VEVENT, VTODO, VCALENDAR, VCARD.
23
+	 *
24
+	 * @var string
25
+	 */
26
+	public $name;
27
+
28
+	/**
29
+	 * A list of properties and/or sub-components.
30
+	 *
31
+	 * @var array
32
+	 */
33
+	protected $children = [];
34
+
35
+	/**
36
+	 * Creates a new component.
37
+	 *
38
+	 * You can specify the children either in key=>value syntax, in which case
39
+	 * properties will automatically be created, or you can just pass a list of
40
+	 * Component and Property object.
41
+	 *
42
+	 * By default, a set of sensible values will be added to the component. For
43
+	 * an iCalendar object, this may be something like CALSCALE:GREGORIAN. To
44
+	 * ensure that this does not happen, set $defaults to false.
45
+	 *
46
+	 * @param Document $root
47
+	 * @param string $name such as VCALENDAR, VEVENT.
48
+	 * @param array $children
49
+	 * @param bool $defaults
50
+	 *
51
+	 * @return void
52
+	 */
53
+	public function __construct(Document $root, $name, array $children = [], $defaults = true) {
54
+
55
+		$this->name = strtoupper($name);
56
+		$this->root = $root;
57
+
58
+		if ($defaults) {
59
+			// This is a terribly convoluted way to do this, but this ensures
60
+			// that the order of properties as they are specified in both
61
+			// defaults and the childrens list, are inserted in the object in a
62
+			// natural way.
63
+			$list = $this->getDefaults();
64
+			$nodes = [];
65
+			foreach ($children as $key => $value) {
66
+				if ($value instanceof Node) {
67
+					if (isset($list[$value->name])) {
68
+						unset($list[$value->name]);
69
+					}
70
+					$nodes[] = $value;
71
+				} else {
72
+					$list[$key] = $value;
73
+				}
74
+			}
75
+			foreach ($list as $key => $value) {
76
+				$this->add($key, $value);
77
+			}
78
+			foreach ($nodes as $node) {
79
+				$this->add($node);
80
+			}
81
+		} else {
82
+			foreach ($children as $k => $child) {
83
+				if ($child instanceof Node) {
84
+					// Component or Property
85
+					$this->add($child);
86
+				} else {
87
+
88
+					// Property key=>value
89
+					$this->add($k, $child);
90
+				}
91
+			}
92
+		}
93
+
94
+	}
95
+
96
+	/**
97
+	 * Adds a new property or component, and returns the new item.
98
+	 *
99
+	 * This method has 3 possible signatures:
100
+	 *
101
+	 * add(Component $comp) // Adds a new component
102
+	 * add(Property $prop)  // Adds a new property
103
+	 * add($name, $value, array $parameters = []) // Adds a new property
104
+	 * add($name, array $children = []) // Adds a new component
105
+	 * by name.
106
+	 *
107
+	 * @return Node
108
+	 */
109
+	public function add() {
110
+
111
+		$arguments = func_get_args();
112
+
113
+		if ($arguments[0] instanceof Node) {
114
+			if (isset($arguments[1])) {
115
+				throw new \InvalidArgumentException('The second argument must not be specified, when passing a VObject Node');
116
+			}
117
+			$arguments[0]->parent = $this;
118
+			$newNode = $arguments[0];
119
+
120
+		} elseif (is_string($arguments[0])) {
121
+
122
+			$newNode = call_user_func_array([$this->root, 'create'], $arguments);
123
+
124
+		} else {
125
+
126
+			throw new \InvalidArgumentException('The first argument must either be a \\Sabre\\VObject\\Node or a string');
127
+
128
+		}
129
+
130
+		$name = $newNode->name;
131
+		if (isset($this->children[$name])) {
132
+			$this->children[$name][] = $newNode;
133
+		} else {
134
+			$this->children[$name] = [$newNode];
135
+		}
136
+		return $newNode;
137
+
138
+	}
139
+
140
+	/**
141
+	 * This method removes a component or property from this component.
142
+	 *
143
+	 * You can either specify the item by name (like DTSTART), in which case
144
+	 * all properties/components with that name will be removed, or you can
145
+	 * pass an instance of a property or component, in which case only that
146
+	 * exact item will be removed.
147
+	 *
148
+	 * @param string|Property|Component $item
149
+	 * @return void
150
+	 */
151
+	public function remove($item) {
152
+
153
+		if (is_string($item)) {
154
+			// If there's no dot in the name, it's an exact property name and
155
+			// we can just wipe out all those properties.
156
+			//
157
+			if (strpos($item, '.') === false) {
158
+				unset($this->children[strtoupper($item)]);
159
+				return;
160
+			}
161
+			// If there was a dot, we need to ask select() to help us out and
162
+			// then we just call remove recursively.
163
+			foreach ($this->select($item) as $child) {
164
+
165
+				$this->remove($child);
166
+
167
+			}
168
+		} else {
169
+			foreach ($this->select($item->name) as $k => $child) {
170
+				if ($child === $item) {
171
+					unset($this->children[$item->name][$k]);
172
+					return;
173
+				}
174
+			}
175
+		}
176
+
177
+		throw new \InvalidArgumentException('The item you passed to remove() was not a child of this component');
178
+
179
+	}
180
+
181
+	/**
182
+	 * Returns a flat list of all the properties and components in this
183
+	 * component.
184
+	 *
185
+	 * @return array
186
+	 */
187
+	public function children() {
188
+
189
+		$result = [];
190
+		foreach ($this->children as $childGroup) {
191
+			$result = array_merge($result, $childGroup);
192
+		}
193
+		return $result;
194
+
195
+	}
196
+
197
+	/**
198
+	 * This method only returns a list of sub-components. Properties are
199
+	 * ignored.
200
+	 *
201
+	 * @return array
202
+	 */
203
+	public function getComponents() {
204
+
205
+		$result = [];
206
+
207
+		foreach ($this->children as $childGroup) {
208
+			foreach ($childGroup as $child) {
209
+				if ($child instanceof self) {
210
+					$result[] = $child;
211
+				}
212
+			}
213
+		}
214
+		return $result;
215
+
216
+	}
217
+
218
+	/**
219
+	 * Returns an array with elements that match the specified name.
220
+	 *
221
+	 * This function is also aware of MIME-Directory groups (as they appear in
222
+	 * vcards). This means that if a property is grouped as "HOME.EMAIL", it
223
+	 * will also be returned when searching for just "EMAIL". If you want to
224
+	 * search for a property in a specific group, you can select on the entire
225
+	 * string ("HOME.EMAIL"). If you want to search on a specific property that
226
+	 * has not been assigned a group, specify ".EMAIL".
227
+	 *
228
+	 * @param string $name
229
+	 * @return array
230
+	 */
231
+	public function select($name) {
232
+
233
+		$group = null;
234
+		$name = strtoupper($name);
235
+		if (strpos($name, '.') !== false) {
236
+			list($group, $name) = explode('.', $name, 2);
237
+		}
238
+		if ($name === '') $name = null;
239
+
240
+		if (!is_null($name)) {
241
+
242
+			$result = isset($this->children[$name]) ? $this->children[$name] : [];
243
+
244
+			if (is_null($group)) {
245
+				return $result;
246
+			} else {
247
+				// If we have a group filter as well, we need to narrow it down
248
+				// more.
249
+				return array_filter(
250
+					$result,
251
+					public function($child) use ($group) {
252
+
253
+						return $child instanceof Property && strtoupper($child->group) === $group;
254
+
255
+					}
256
+				);
257
+			}
258
+
259
+		}
260
+
261
+		// If we got to this point, it means there was no 'name' specified for
262
+		// searching, implying that this is a group-only search.
263
+		$result = [];
264
+		foreach ($this->children as $childGroup) {
265
+
266
+			foreach ($childGroup as $child) {
267
+
268
+				if ($child instanceof Property && strtoupper($child->group) === $group) {
269
+					$result[] = $child;
270
+				}
271
+
272
+			}
273
+
274
+		}
275
+		return $result;
276
+
277
+	}
278
+
279
+	/**
280
+	 * Turns the object back into a serialized blob.
281
+	 *
282
+	 * @return string
283
+	 */
284
+	public function serialize() {
285
+
286
+		$str = "BEGIN:" . $this->name . "\r\n";
287
+
288
+		/**
289
+		 * Gives a component a 'score' for sorting purposes.
290
+		 *
291
+		 * This is solely used by the childrenSort method.
292
+		 *
293
+		 * A higher score means the item will be lower in the list.
294
+		 * To avoid score collisions, each "score category" has a reasonable
295
+		 * space to accomodate elements. The $key is added to the $score to
296
+		 * preserve the original relative order of elements.
297
+		 *
298
+		 * @param int $key
299
+		 * @param array $array
300
+		 *
301
+		 * @return int
302
+		 */
303
+		$sortScore = function($key, $array) {
304
+
305
+			if ($array[$key] instanceof Component) {
306
+
307
+				// We want to encode VTIMEZONE first, this is a personal
308
+				// preference.
309
+				if ($array[$key]->name === 'VTIMEZONE') {
310
+					$score = 300000000;
311
+					return $score + $key;
312
+				} else {
313
+					$score = 400000000;
314
+					return $score + $key;
315
+				}
316
+			} else {
317
+				// Properties get encoded first
318
+				// VCARD version 4.0 wants the VERSION property to appear first
319
+				if ($array[$key] instanceof Property) {
320
+					if ($array[$key]->name === 'VERSION') {
321
+						$score = 100000000;
322
+						return $score + $key;
323
+					} else {
324
+						// All other properties
325
+						$score = 200000000;
326
+						return $score + $key;
327
+					}
328
+				}
329
+			}
330
+
331
+		};
332
+
333
+		$children = $this->children();
334
+		$tmp = $children;
335
+		uksort(
336
+			$children,
337
+			public function($a, $b) use ($sortScore, $tmp) {
338
+
339
+				$sA = $sortScore($a, $tmp);
340
+				$sB = $sortScore($b, $tmp);
341
+
342
+				return $sA - $sB;
343
+
344
+			}
345
+		);
346
+
347
+		foreach ($children as $child) $str .= $child->serialize();
348
+		$str .= "END:" . $this->name . "\r\n";
349
+
350
+		return $str;
351
+
352
+	}
353
+
354
+	/**
355
+	 * This method returns an array, with the representation as it should be
356
+	 * encoded in JSON. This is used to create jCard or jCal documents.
357
+	 *
358
+	 * @return array
359
+	 */
360
+	public function jsonSerialize() {
361
+
362
+		$components = [];
363
+		$properties = [];
364
+
365
+		foreach ($this->children as $childGroup) {
366
+			foreach ($childGroup as $child) {
367
+				if ($child instanceof self) {
368
+					$components[] = $child->jsonSerialize();
369
+				} else {
370
+					$properties[] = $child->jsonSerialize();
371
+				}
372
+			}
373
+		}
374
+
375
+		return [
376
+			strtolower($this->name),
377
+			$properties,
378
+			$components
379
+		];
380
+
381
+	}
382
+
383
+	/**
384
+	 * This method serializes the data into XML. This is used to create xCard or
385
+	 * xCal documents.
386
+	 *
387
+	 * @param Xml\Writer $writer  XML writer.
388
+	 *
389
+	 * @return void
390
+	 */
391
+	public function xmlSerialize(Xml\Writer $writer) {
392
+
393
+		$components = [];
394
+		$properties = [];
395
+
396
+		foreach ($this->children as $childGroup) {
397
+			foreach ($childGroup as $child) {
398
+				if ($child instanceof self) {
399
+					$components[] = $child;
400
+				} else {
401
+					$properties[] = $child;
402
+				}
403
+			}
404
+		}
405
+
406
+		$writer->startElement(strtolower($this->name));
407
+
408
+		if (!empty($properties)) {
409
+
410
+			$writer->startElement('properties');
411
+
412
+			foreach ($properties as $property) {
413
+				$property->xmlSerialize($writer);
414
+			}
415
+
416
+			$writer->endElement();
417
+
418
+		}
419
+
420
+		if (!empty($components)) {
421
+
422
+			$writer->startElement('components');
423
+
424
+			foreach ($components as $component) {
425
+				$component->xmlSerialize($writer);
426
+			}
427
+
428
+			$writer->endElement();
429
+		}
430
+
431
+		$writer->endElement();
432
+
433
+	}
434
+
435
+	/**
436
+	 * This method should return a list of default property values.
437
+	 *
438
+	 * @return array
439
+	 */
440
+	protected function getDefaults() {
441
+
442
+		return [];
443
+
444
+	}
445
+
446
+	/* Magic property accessors {{{ */
447
+
448
+	/**
449
+	 * Using 'get' you will either get a property or component.
450
+	 *
451
+	 * If there were no child-elements found with the specified name,
452
+	 * null is returned.
453
+	 *
454
+	 * To use this, this may look something like this:
455
+	 *
456
+	 * $event = $calendar->VEVENT;
457
+	 *
458
+	 * @param string $name
459
+	 *
460
+	 * @return Property
461
+	 */
462
+	public function __get($name) {
463
+
464
+		if ($name === 'children') {
465
+
466
+			throw new \RuntimeException('Starting sabre/vobject 4.0 the children property is now protected. You should use the children() method instead');
467
+
468
+		}
469
+
470
+		$matches = $this->select($name);
471
+		if (count($matches) === 0) {
472
+			return;
473
+		} else {
474
+			$firstMatch = current($matches);
475
+			/** @var $firstMatch Property */
476
+			$firstMatch->setIterator(new ElementList(array_values($matches)));
477
+			return $firstMatch;
478
+		}
479
+
480
+	}
481
+
482
+	/**
483
+	 * This method checks if a sub-element with the specified name exists.
484
+	 *
485
+	 * @param string $name
486
+	 *
487
+	 * @return bool
488
+	 */
489
+	public function __isset($name) {
490
+
491
+		$matches = $this->select($name);
492
+		return count($matches) > 0;
493
+
494
+	}
495
+
496
+	/**
497
+	 * Using the setter method you can add properties or subcomponents.
498
+	 *
499
+	 * You can either pass a Component, Property
500
+	 * object, or a string to automatically create a Property.
501
+	 *
502
+	 * If the item already exists, it will be removed. If you want to add
503
+	 * a new item with the same name, always use the add() method.
504
+	 *
505
+	 * @param string $name
506
+	 * @param mixed $value
507
+	 *
508
+	 * @return void
509
+	 */
510
+	public function __set($name, $value) {
511
+
512
+		$name = strtoupper($name);
513
+		$this->remove($name);
514
+		if ($value instanceof self || $value instanceof Property) {
515
+			$this->add($value);
516
+		} else {
517
+			$this->add($name, $value);
518
+		}
519
+	}
520
+
521
+	/**
522
+	 * Removes all properties and components within this component with the
523
+	 * specified name.
524
+	 *
525
+	 * @param string $name
526
+	 *
527
+	 * @return void
528
+	 */
529
+	public function __unset($name) {
530
+
531
+		$this->remove($name);
532
+
533
+	}
534
+
535
+	/* }}} */
536
+
537
+	/**
538
+	 * This method is automatically called when the object is cloned.
539
+	 * Specifically, this will ensure all child elements are also cloned.
540
+	 *
541
+	 * @return void
542
+	 */
543
+	public function __clone() {
544
+
545
+		foreach ($this->children as $childName => $childGroup) {
546
+			foreach ($childGroup as $key => $child) {
547
+				$clonedChild = clone $child;
548
+				$clonedChild->parent = $this;
549
+				$clonedChild->root = $this->root;
550
+				$this->children[$childName][$key] = $clonedChild;
551
+			}
552
+		}
553
+
554
+	}
555
+
556
+	/**
557
+	 * A simple list of validation rules.
558
+	 *
559
+	 * This is simply a list of properties, and how many times they either
560
+	 * must or must not appear.
561
+	 *
562
+	 * Possible values per property:
563
+	 *   * 0 - Must not appear.
564
+	 *   * 1 - Must appear exactly once.
565
+	 *   * + - Must appear at least once.
566
+	 *   * * - Can appear any number of times.
567
+	 *   * ? - May appear, but not more than once.
568
+	 *
569
+	 * It is also possible to specify defaults and severity levels for
570
+	 * violating the rule.
571
+	 *
572
+	 * See the VEVENT implementation for getValidationRules for a more complex
573
+	 * example.
574
+	 *
575
+	 * @var array
576
+	 */
577
+	public function getValidationRules() {
578
+
579
+		return [];
580
+
581
+	}
582
+
583
+	/**
584
+	 * Validates the node for correctness.
585
+	 *
586
+	 * The following options are supported:
587
+	 *   Node::REPAIR - May attempt to automatically repair the problem.
588
+	 *   Node::PROFILE_CARDDAV - Validate the vCard for CardDAV purposes.
589
+	 *   Node::PROFILE_CALDAV - Validate the iCalendar for CalDAV purposes.
590
+	 *
591
+	 * This method returns an array with detected problems.
592
+	 * Every element has the following properties:
593
+	 *
594
+	 *  * level - problem level.
595
+	 *  * message - A human-readable string describing the issue.
596
+	 *  * node - A reference to the problematic node.
597
+	 *
598
+	 * The level means:
599
+	 *   1 - The issue was repaired (only happens if REPAIR was turned on).
600
+	 *   2 - A warning.
601
+	 *   3 - An error.
602
+	 *
603
+	 * @param int $options
604
+	 *
605
+	 * @return array
606
+	 */
607
+	public function validate($options = 0) {
608
+
609
+		$rules = $this->getValidationRules();
610
+		$defaults = $this->getDefaults();
611
+
612
+		$propertyCounters = [];
613
+
614
+		$messages = [];
615
+
616
+		foreach ($this->children() as $child) {
617
+			$name = strtoupper($child->name);
618
+			if (!isset($propertyCounters[$name])) {
619
+				$propertyCounters[$name] = 1;
620
+			} else {
621
+				$propertyCounters[$name]++;
622
+			}
623
+			$messages = array_merge($messages, $child->validate($options));
624
+		}
625
+
626
+		foreach ($rules as $propName => $rule) {
627
+
628
+			switch ($rule) {
629
+				case '0' :
630
+					if (isset($propertyCounters[$propName])) {
631
+						$messages[] = [
632
+							'level'   => 3,
633
+							'message' => $propName . ' MUST NOT appear in a ' . $this->name . ' component',
634
+							'node'    => $this,
635
+						];
636
+					}
637
+					break;
638
+				case '1' :
639
+					if (!isset($propertyCounters[$propName]) || $propertyCounters[$propName] !== 1) {
640
+						$repaired = false;
641
+						if ($options & self::REPAIR && isset($defaults[$propName])) {
642
+							$this->add($propName, $defaults[$propName]);
643
+							$repaired = true;
644
+						}
645
+						$messages[] = [
646
+							'level'   => $repaired ? 1 : 3,
647
+							'message' => $propName . ' MUST appear exactly once in a ' . $this->name . ' component',
648
+							'node'    => $this,
649
+						];
650
+					}
651
+					break;
652
+				case '+' :
653
+					if (!isset($propertyCounters[$propName]) || $propertyCounters[$propName] < 1) {
654
+						$messages[] = [
655
+							'level'   => 3,
656
+							'message' => $propName . ' MUST appear at least once in a ' . $this->name . ' component',
657
+							'node'    => $this,
658
+						];
659
+					}
660
+					break;
661
+				case '*' :
662
+					break;
663
+				case '?' :
664
+					if (isset($propertyCounters[$propName]) && $propertyCounters[$propName] > 1) {
665
+						$messages[] = [
666
+							'level'   => 3,
667
+							'message' => $propName . ' MUST NOT appear more than once in a ' . $this->name . ' component',
668
+							'node'    => $this,
669
+						];
670
+					}
671
+					break;
672
+
673
+			}
674
+
675
+		}
676
+		return $messages;
677
+
678
+	}
679
+
680
+	/**
681
+	 * Call this method on a document if you're done using it.
682
+	 *
683
+	 * It's intended to remove all circular references, so PHP can easily clean
684
+	 * it up.
685
+	 *
686
+	 * @return void
687
+	 */
688
+	public function destroy() {
689
+
690
+		parent::destroy();
691
+		foreach ($this->children as $childGroup) {
692
+			foreach ($childGroup as $child) {
693
+				$child->destroy();
694
+			}
695
+		}
696
+		$this->children = [];
697
+
698
+	}
699 699
 
700 700
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/VObject/Document.php 1 patch
Indentation   +248 added lines, -248 removed lines patch added patch discarded remove patch
@@ -18,253 +18,253 @@
 block discarded – undo
18 18
  */
19 19
 abstract class Document extends Component {
20 20
 
21
-    /**
22
-     * Unknown document type.
23
-     */
24
-    const UNKNOWN = 1;
25
-
26
-    /**
27
-     * vCalendar 1.0.
28
-     */
29
-    const VCALENDAR10 = 2;
30
-
31
-    /**
32
-     * iCalendar 2.0.
33
-     */
34
-    const ICALENDAR20 = 3;
35
-
36
-    /**
37
-     * vCard 2.1.
38
-     */
39
-    const VCARD21 = 4;
40
-
41
-    /**
42
-     * vCard 3.0.
43
-     */
44
-    const VCARD30 = 5;
45
-
46
-    /**
47
-     * vCard 4.0.
48
-     */
49
-    const VCARD40 = 6;
50
-
51
-    /**
52
-     * The default name for this component.
53
-     *
54
-     * This should be 'VCALENDAR' or 'VCARD'.
55
-     *
56
-     * @var string
57
-     */
58
-    static $defaultName;
59
-
60
-    /**
61
-     * List of properties, and which classes they map to.
62
-     *
63
-     * @var array
64
-     */
65
-    static $propertyMap = [];
66
-
67
-    /**
68
-     * List of components, along with which classes they map to.
69
-     *
70
-     * @var array
71
-     */
72
-    static $componentMap = [];
73
-
74
-    /**
75
-     * List of value-types, and which classes they map to.
76
-     *
77
-     * @var array
78
-     */
79
-    static $valueMap = [];
80
-
81
-    /**
82
-     * Creates a new document.
83
-     *
84
-     * We're changing the default behavior slightly here. First, we don't want
85
-     * to have to specify a name (we already know it), and we want to allow
86
-     * children to be specified in the first argument.
87
-     *
88
-     * But, the default behavior also works.
89
-     *
90
-     * So the two sigs:
91
-     *
92
-     * new Document(array $children = [], $defaults = true);
93
-     * new Document(string $name, array $children = [], $defaults = true)
94
-     *
95
-     * @return void
96
-     */
97
-    public function __construct() {
98
-
99
-        $args = func_get_args();
100
-        if (count($args) === 0 || is_array($args[0])) {
101
-            array_unshift($args, $this, static::$defaultName);
102
-            call_user_func_array(['parent', '__construct'], $args);
103
-        } else {
104
-            array_unshift($args, $this);
105
-            call_user_func_array(['parent', '__construct'], $args);
106
-        }
107
-
108
-    }
109
-
110
-    /**
111
-     * Returns the current document type.
112
-     *
113
-     * @return int
114
-     */
115
-    public function getDocumentType() {
116
-
117
-        return self::UNKNOWN;
118
-
119
-    }
120
-
121
-    /**
122
-     * Creates a new component or property.
123
-     *
124
-     * If it's a known component, we will automatically call createComponent.
125
-     * otherwise, we'll assume it's a property and call createProperty instead.
126
-     *
127
-     * @param string $name
128
-     * @param string $arg1,... Unlimited number of args
129
-     *
130
-     * @return mixed
131
-     */
132
-    public function create($name) {
133
-
134
-        if (isset(static::$componentMap[strtoupper($name)])) {
135
-
136
-            return call_user_func_array([$this, 'createComponent'], func_get_args());
137
-
138
-        } else {
139
-
140
-            return call_user_func_array([$this, 'createProperty'], func_get_args());
141
-
142
-        }
143
-
144
-    }
145
-
146
-    /**
147
-     * Creates a new component.
148
-     *
149
-     * This method automatically searches for the correct component class, based
150
-     * on its name.
151
-     *
152
-     * You can specify the children either in key=>value syntax, in which case
153
-     * properties will automatically be created, or you can just pass a list of
154
-     * Component and Property object.
155
-     *
156
-     * By default, a set of sensible values will be added to the component. For
157
-     * an iCalendar object, this may be something like CALSCALE:GREGORIAN. To
158
-     * ensure that this does not happen, set $defaults to false.
159
-     *
160
-     * @param string $name
161
-     * @param array $children
162
-     * @param bool $defaults
163
-     *
164
-     * @return Component
165
-     */
166
-    public function createComponent($name, array $children = null, $defaults = true) {
167
-
168
-        $name = strtoupper($name);
169
-        $class = 'Sabre\\VObject\\Component';
170
-
171
-        if (isset(static::$componentMap[$name])) {
172
-            $class = static::$componentMap[$name];
173
-        }
174
-        if (is_null($children)) $children = [];
175
-        return new $class($this, $name, $children, $defaults);
176
-
177
-    }
178
-
179
-    /**
180
-     * Factory method for creating new properties.
181
-     *
182
-     * This method automatically searches for the correct property class, based
183
-     * on its name.
184
-     *
185
-     * You can specify the parameters either in key=>value syntax, in which case
186
-     * parameters will automatically be created, or you can just pass a list of
187
-     * Parameter objects.
188
-     *
189
-     * @param string $name
190
-     * @param mixed $value
191
-     * @param array $parameters
192
-     * @param string $valueType Force a specific valuetype, such as URI or TEXT
193
-     *
194
-     * @return Property
195
-     */
196
-    public function createProperty($name, $value = null, array $parameters = null, $valueType = null) {
197
-
198
-        // If there's a . in the name, it means it's prefixed by a groupname.
199
-        if (($i = strpos($name, '.')) !== false) {
200
-            $group = substr($name, 0, $i);
201
-            $name = strtoupper(substr($name, $i + 1));
202
-        } else {
203
-            $name = strtoupper($name);
204
-            $group = null;
205
-        }
206
-
207
-        $class = null;
208
-
209
-        if ($valueType) {
210
-            // The valueType argument comes first to figure out the correct
211
-            // class.
212
-            $class = $this->getClassNameForPropertyValue($valueType);
213
-        }
214
-
215
-        if (is_null($class)) {
216
-            // If a VALUE parameter is supplied, we should use that.
217
-            if (isset($parameters['VALUE'])) {
218
-                $class = $this->getClassNameForPropertyValue($parameters['VALUE']);
219
-                if (is_null($class)) {
220
-                    throw new InvalidDataException('Unsupported VALUE parameter for ' . $name . ' property. You supplied "' . $parameters['VALUE'] . '"');
221
-                }
222
-            }
223
-            else {
224
-                $class = $this->getClassNameForPropertyName($name);
225
-            }
226
-        }
227
-        if (is_null($parameters)) $parameters = [];
228
-
229
-        return new $class($this, $name, $value, $parameters, $group);
230
-
231
-    }
232
-
233
-    /**
234
-     * This method returns a full class-name for a value parameter.
235
-     *
236
-     * For instance, DTSTART may have VALUE=DATE. In that case we will look in
237
-     * our valueMap table and return the appropriate class name.
238
-     *
239
-     * This method returns null if we don't have a specialized class.
240
-     *
241
-     * @param string $valueParam
242
-     * @return string|null
243
-     */
244
-    public function getClassNameForPropertyValue($valueParam) {
245
-
246
-        $valueParam = strtoupper($valueParam);
247
-        if (isset(static::$valueMap[$valueParam])) {
248
-            return static::$valueMap[$valueParam];
249
-        }
250
-
251
-    }
252
-
253
-    /**
254
-     * Returns the default class for a property name.
255
-     *
256
-     * @param string $propertyName
257
-     *
258
-     * @return string
259
-     */
260
-    public function getClassNameForPropertyName($propertyName) {
261
-
262
-        if (isset(static::$propertyMap[$propertyName])) {
263
-            return static::$propertyMap[$propertyName];
264
-        } else {
265
-            return 'Sabre\\VObject\\Property\\Unknown';
266
-        }
267
-
268
-    }
21
+	/**
22
+	 * Unknown document type.
23
+	 */
24
+	const UNKNOWN = 1;
25
+
26
+	/**
27
+	 * vCalendar 1.0.
28
+	 */
29
+	const VCALENDAR10 = 2;
30
+
31
+	/**
32
+	 * iCalendar 2.0.
33
+	 */
34
+	const ICALENDAR20 = 3;
35
+
36
+	/**
37
+	 * vCard 2.1.
38
+	 */
39
+	const VCARD21 = 4;
40
+
41
+	/**
42
+	 * vCard 3.0.
43
+	 */
44
+	const VCARD30 = 5;
45
+
46
+	/**
47
+	 * vCard 4.0.
48
+	 */
49
+	const VCARD40 = 6;
50
+
51
+	/**
52
+	 * The default name for this component.
53
+	 *
54
+	 * This should be 'VCALENDAR' or 'VCARD'.
55
+	 *
56
+	 * @var string
57
+	 */
58
+	static $defaultName;
59
+
60
+	/**
61
+	 * List of properties, and which classes they map to.
62
+	 *
63
+	 * @var array
64
+	 */
65
+	static $propertyMap = [];
66
+
67
+	/**
68
+	 * List of components, along with which classes they map to.
69
+	 *
70
+	 * @var array
71
+	 */
72
+	static $componentMap = [];
73
+
74
+	/**
75
+	 * List of value-types, and which classes they map to.
76
+	 *
77
+	 * @var array
78
+	 */
79
+	static $valueMap = [];
80
+
81
+	/**
82
+	 * Creates a new document.
83
+	 *
84
+	 * We're changing the default behavior slightly here. First, we don't want
85
+	 * to have to specify a name (we already know it), and we want to allow
86
+	 * children to be specified in the first argument.
87
+	 *
88
+	 * But, the default behavior also works.
89
+	 *
90
+	 * So the two sigs:
91
+	 *
92
+	 * new Document(array $children = [], $defaults = true);
93
+	 * new Document(string $name, array $children = [], $defaults = true)
94
+	 *
95
+	 * @return void
96
+	 */
97
+	public function __construct() {
98
+
99
+		$args = func_get_args();
100
+		if (count($args) === 0 || is_array($args[0])) {
101
+			array_unshift($args, $this, static::$defaultName);
102
+			call_user_func_array(['parent', '__construct'], $args);
103
+		} else {
104
+			array_unshift($args, $this);
105
+			call_user_func_array(['parent', '__construct'], $args);
106
+		}
107
+
108
+	}
109
+
110
+	/**
111
+	 * Returns the current document type.
112
+	 *
113
+	 * @return int
114
+	 */
115
+	public function getDocumentType() {
116
+
117
+		return self::UNKNOWN;
118
+
119
+	}
120
+
121
+	/**
122
+	 * Creates a new component or property.
123
+	 *
124
+	 * If it's a known component, we will automatically call createComponent.
125
+	 * otherwise, we'll assume it's a property and call createProperty instead.
126
+	 *
127
+	 * @param string $name
128
+	 * @param string $arg1,... Unlimited number of args
129
+	 *
130
+	 * @return mixed
131
+	 */
132
+	public function create($name) {
133
+
134
+		if (isset(static::$componentMap[strtoupper($name)])) {
135
+
136
+			return call_user_func_array([$this, 'createComponent'], func_get_args());
137
+
138
+		} else {
139
+
140
+			return call_user_func_array([$this, 'createProperty'], func_get_args());
141
+
142
+		}
143
+
144
+	}
145
+
146
+	/**
147
+	 * Creates a new component.
148
+	 *
149
+	 * This method automatically searches for the correct component class, based
150
+	 * on its name.
151
+	 *
152
+	 * You can specify the children either in key=>value syntax, in which case
153
+	 * properties will automatically be created, or you can just pass a list of
154
+	 * Component and Property object.
155
+	 *
156
+	 * By default, a set of sensible values will be added to the component. For
157
+	 * an iCalendar object, this may be something like CALSCALE:GREGORIAN. To
158
+	 * ensure that this does not happen, set $defaults to false.
159
+	 *
160
+	 * @param string $name
161
+	 * @param array $children
162
+	 * @param bool $defaults
163
+	 *
164
+	 * @return Component
165
+	 */
166
+	public function createComponent($name, array $children = null, $defaults = true) {
167
+
168
+		$name = strtoupper($name);
169
+		$class = 'Sabre\\VObject\\Component';
170
+
171
+		if (isset(static::$componentMap[$name])) {
172
+			$class = static::$componentMap[$name];
173
+		}
174
+		if (is_null($children)) $children = [];
175
+		return new $class($this, $name, $children, $defaults);
176
+
177
+	}
178
+
179
+	/**
180
+	 * Factory method for creating new properties.
181
+	 *
182
+	 * This method automatically searches for the correct property class, based
183
+	 * on its name.
184
+	 *
185
+	 * You can specify the parameters either in key=>value syntax, in which case
186
+	 * parameters will automatically be created, or you can just pass a list of
187
+	 * Parameter objects.
188
+	 *
189
+	 * @param string $name
190
+	 * @param mixed $value
191
+	 * @param array $parameters
192
+	 * @param string $valueType Force a specific valuetype, such as URI or TEXT
193
+	 *
194
+	 * @return Property
195
+	 */
196
+	public function createProperty($name, $value = null, array $parameters = null, $valueType = null) {
197
+
198
+		// If there's a . in the name, it means it's prefixed by a groupname.
199
+		if (($i = strpos($name, '.')) !== false) {
200
+			$group = substr($name, 0, $i);
201
+			$name = strtoupper(substr($name, $i + 1));
202
+		} else {
203
+			$name = strtoupper($name);
204
+			$group = null;
205
+		}
206
+
207
+		$class = null;
208
+
209
+		if ($valueType) {
210
+			// The valueType argument comes first to figure out the correct
211
+			// class.
212
+			$class = $this->getClassNameForPropertyValue($valueType);
213
+		}
214
+
215
+		if (is_null($class)) {
216
+			// If a VALUE parameter is supplied, we should use that.
217
+			if (isset($parameters['VALUE'])) {
218
+				$class = $this->getClassNameForPropertyValue($parameters['VALUE']);
219
+				if (is_null($class)) {
220
+					throw new InvalidDataException('Unsupported VALUE parameter for ' . $name . ' property. You supplied "' . $parameters['VALUE'] . '"');
221
+				}
222
+			}
223
+			else {
224
+				$class = $this->getClassNameForPropertyName($name);
225
+			}
226
+		}
227
+		if (is_null($parameters)) $parameters = [];
228
+
229
+		return new $class($this, $name, $value, $parameters, $group);
230
+
231
+	}
232
+
233
+	/**
234
+	 * This method returns a full class-name for a value parameter.
235
+	 *
236
+	 * For instance, DTSTART may have VALUE=DATE. In that case we will look in
237
+	 * our valueMap table and return the appropriate class name.
238
+	 *
239
+	 * This method returns null if we don't have a specialized class.
240
+	 *
241
+	 * @param string $valueParam
242
+	 * @return string|null
243
+	 */
244
+	public function getClassNameForPropertyValue($valueParam) {
245
+
246
+		$valueParam = strtoupper($valueParam);
247
+		if (isset(static::$valueMap[$valueParam])) {
248
+			return static::$valueMap[$valueParam];
249
+		}
250
+
251
+	}
252
+
253
+	/**
254
+	 * Returns the default class for a property name.
255
+	 *
256
+	 * @param string $propertyName
257
+	 *
258
+	 * @return string
259
+	 */
260
+	public function getClassNameForPropertyName($propertyName) {
261
+
262
+		if (isset(static::$propertyMap[$propertyName])) {
263
+			return static::$propertyMap[$propertyName];
264
+		} else {
265
+			return 'Sabre\\VObject\\Property\\Unknown';
266
+		}
267
+
268
+	}
269 269
 
270 270
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/VObject/BirthdayCalendarGenerator.php 1 patch
Indentation   +168 added lines, -168 removed lines patch added patch discarded remove patch
@@ -13,179 +13,179 @@
 block discarded – undo
13 13
  */
14 14
 class BirthdayCalendarGenerator {
15 15
 
16
-    /**
17
-     * Input objects.
18
-     *
19
-     * @var array
20
-     */
21
-    protected $objects = [];
16
+	/**
17
+	 * Input objects.
18
+	 *
19
+	 * @var array
20
+	 */
21
+	protected $objects = [];
22 22
 
23
-    /**
24
-     * Default year.
25
-     * Used for dates without a year.
26
-     */
27
-    const DEFAULT_YEAR = 2000;
28
-
29
-    /**
30
-     * Output format for the SUMMARY.
31
-     *
32
-     * @var string
33
-     */
34
-    protected $format = '%1$s\'s Birthday';
35
-
36
-    /**
37
-     * Creates the generator.
38
-     *
39
-     * Check the setTimeRange and setObjects methods for details about the
40
-     * arguments.
41
-     *
42
-     * @param mixed $objects
43
-     */
44
-    public function __construct($objects = null) {
45
-
46
-        if ($objects) {
47
-            $this->setObjects($objects);
48
-        }
49
-
50
-    }
51
-
52
-    /**
53
-     * Sets the input objects.
54
-     *
55
-     * You must either supply a vCard as a string or as a Component/VCard object.
56
-     * It's also possible to supply an array of strings or objects.
57
-     *
58
-     * @param mixed $objects
59
-     *
60
-     * @return void
61
-     */
62
-    public function setObjects($objects) {
63
-
64
-        if (!is_array($objects)) {
65
-            $objects = [$objects];
66
-        }
67
-
68
-        $this->objects = [];
69
-        foreach ($objects as $object) {
70
-
71
-            if (is_string($object)) {
72
-
73
-                $vObj = Reader::read($object);
74
-                if (!$vObj instanceof Component\VCard) {
75
-                    throw new \InvalidArgumentException('String could not be parsed as \\Sabre\\VObject\\Component\\VCard by setObjects');
76
-                }
77
-
78
-                $this->objects[] = $vObj;
79
-
80
-            } elseif ($object instanceof Component\VCard) {
81
-
82
-                $this->objects[] = $object;
83
-
84
-            } else {
85
-
86
-                throw new \InvalidArgumentException('You can only pass strings or \\Sabre\\VObject\\Component\\VCard arguments to setObjects');
87
-
88
-            }
89
-
90
-        }
91
-
92
-    }
93
-
94
-    /**
95
-     * Sets the output format for the SUMMARY
96
-     *
97
-     * @param string $format
98
-     *
99
-     * @return void
100
-     */
101
-    public function setFormat($format) {
102
-
103
-        $this->format = $format;
104
-
105
-    }
106
-
107
-    /**
108
-     * Parses the input data and returns a VCALENDAR.
109
-     *
110
-     * @return Component/VCalendar
111
-     */
112
-    public function getResult() {
113
-
114
-        $calendar = new VCalendar();
115
-
116
-        foreach ($this->objects as $object) {
117
-
118
-            // Skip if there is no BDAY property.
119
-            if (!$object->select('BDAY')) {
120
-                continue;
121
-            }
122
-
123
-            // We've seen clients (ez-vcard) putting "BDAY:" properties
124
-            // without a value into vCards. If we come across those, we'll
125
-            // skip them.
126
-            if (empty($object->BDAY->getValue())) {
127
-                continue;
128
-            }
129
-
130
-            // We're always converting to vCard 4.0 so we can rely on the
131
-            // VCardConverter handling the X-APPLE-OMIT-YEAR property for us.
132
-            $object = $object->convert(Document::VCARD40);
133
-
134
-            // Skip if the card has no FN property.
135
-            if (!isset($object->FN)) {
136
-                continue;
137
-            }
138
-
139
-            // Skip if the BDAY property is not of the right type.
140
-            if (!$object->BDAY instanceof Property\VCard\DateAndOrTime) {
141
-                continue;
142
-            }
23
+	/**
24
+	 * Default year.
25
+	 * Used for dates without a year.
26
+	 */
27
+	const DEFAULT_YEAR = 2000;
28
+
29
+	/**
30
+	 * Output format for the SUMMARY.
31
+	 *
32
+	 * @var string
33
+	 */
34
+	protected $format = '%1$s\'s Birthday';
35
+
36
+	/**
37
+	 * Creates the generator.
38
+	 *
39
+	 * Check the setTimeRange and setObjects methods for details about the
40
+	 * arguments.
41
+	 *
42
+	 * @param mixed $objects
43
+	 */
44
+	public function __construct($objects = null) {
45
+
46
+		if ($objects) {
47
+			$this->setObjects($objects);
48
+		}
49
+
50
+	}
51
+
52
+	/**
53
+	 * Sets the input objects.
54
+	 *
55
+	 * You must either supply a vCard as a string or as a Component/VCard object.
56
+	 * It's also possible to supply an array of strings or objects.
57
+	 *
58
+	 * @param mixed $objects
59
+	 *
60
+	 * @return void
61
+	 */
62
+	public function setObjects($objects) {
63
+
64
+		if (!is_array($objects)) {
65
+			$objects = [$objects];
66
+		}
67
+
68
+		$this->objects = [];
69
+		foreach ($objects as $object) {
70
+
71
+			if (is_string($object)) {
72
+
73
+				$vObj = Reader::read($object);
74
+				if (!$vObj instanceof Component\VCard) {
75
+					throw new \InvalidArgumentException('String could not be parsed as \\Sabre\\VObject\\Component\\VCard by setObjects');
76
+				}
77
+
78
+				$this->objects[] = $vObj;
79
+
80
+			} elseif ($object instanceof Component\VCard) {
81
+
82
+				$this->objects[] = $object;
83
+
84
+			} else {
85
+
86
+				throw new \InvalidArgumentException('You can only pass strings or \\Sabre\\VObject\\Component\\VCard arguments to setObjects');
87
+
88
+			}
89
+
90
+		}
91
+
92
+	}
93
+
94
+	/**
95
+	 * Sets the output format for the SUMMARY
96
+	 *
97
+	 * @param string $format
98
+	 *
99
+	 * @return void
100
+	 */
101
+	public function setFormat($format) {
102
+
103
+		$this->format = $format;
104
+
105
+	}
106
+
107
+	/**
108
+	 * Parses the input data and returns a VCALENDAR.
109
+	 *
110
+	 * @return Component/VCalendar
111
+	 */
112
+	public function getResult() {
113
+
114
+		$calendar = new VCalendar();
115
+
116
+		foreach ($this->objects as $object) {
117
+
118
+			// Skip if there is no BDAY property.
119
+			if (!$object->select('BDAY')) {
120
+				continue;
121
+			}
122
+
123
+			// We've seen clients (ez-vcard) putting "BDAY:" properties
124
+			// without a value into vCards. If we come across those, we'll
125
+			// skip them.
126
+			if (empty($object->BDAY->getValue())) {
127
+				continue;
128
+			}
129
+
130
+			// We're always converting to vCard 4.0 so we can rely on the
131
+			// VCardConverter handling the X-APPLE-OMIT-YEAR property for us.
132
+			$object = $object->convert(Document::VCARD40);
133
+
134
+			// Skip if the card has no FN property.
135
+			if (!isset($object->FN)) {
136
+				continue;
137
+			}
138
+
139
+			// Skip if the BDAY property is not of the right type.
140
+			if (!$object->BDAY instanceof Property\VCard\DateAndOrTime) {
141
+				continue;
142
+			}
143 143
 
144
-            // Skip if we can't parse the BDAY value.
145
-            try {
146
-                $dateParts = DateTimeParser::parseVCardDateTime($object->BDAY->getValue());
147
-            } catch (InvalidDataException $e) {
148
-                continue;
149
-            }
150
-
151
-            // Set a year if it's not set.
152
-            $unknownYear = false;
153
-
154
-            if (!$dateParts['year']) {
155
-                $object->BDAY = self::DEFAULT_YEAR . '-' . $dateParts['month'] . '-' . $dateParts['date'];
144
+			// Skip if we can't parse the BDAY value.
145
+			try {
146
+				$dateParts = DateTimeParser::parseVCardDateTime($object->BDAY->getValue());
147
+			} catch (InvalidDataException $e) {
148
+				continue;
149
+			}
150
+
151
+			// Set a year if it's not set.
152
+			$unknownYear = false;
153
+
154
+			if (!$dateParts['year']) {
155
+				$object->BDAY = self::DEFAULT_YEAR . '-' . $dateParts['month'] . '-' . $dateParts['date'];
156 156
 
157
-                $unknownYear = true;
158
-            }
157
+				$unknownYear = true;
158
+			}
159 159
 
160
-            // Create event.
161
-            $event = $calendar->add('VEVENT', [
162
-                'SUMMARY'      => sprintf($this->format, $object->FN->getValue()),
163
-                'DTSTART'      => new \DateTime($object->BDAY->getValue()),
164
-                'RRULE'        => 'FREQ=YEARLY',
165
-                'TRANSP'       => 'TRANSPARENT',
166
-            ]);
160
+			// Create event.
161
+			$event = $calendar->add('VEVENT', [
162
+				'SUMMARY'      => sprintf($this->format, $object->FN->getValue()),
163
+				'DTSTART'      => new \DateTime($object->BDAY->getValue()),
164
+				'RRULE'        => 'FREQ=YEARLY',
165
+				'TRANSP'       => 'TRANSPARENT',
166
+			]);
167 167
 
168
-            // add VALUE=date
169
-            $event->DTSTART['VALUE'] = 'DATE';
168
+			// add VALUE=date
169
+			$event->DTSTART['VALUE'] = 'DATE';
170 170
 
171
-            // Add X-SABRE-BDAY property.
172
-            if ($unknownYear) {
173
-                $event->add('X-SABRE-BDAY', 'BDAY', [
174
-                    'X-SABRE-VCARD-UID' => $object->UID->getValue(),
175
-                    'X-SABRE-VCARD-FN'  => $object->FN->getValue(),
176
-                    'X-SABRE-OMIT-YEAR' => self::DEFAULT_YEAR,
177
-                ]);
178
-            } else {
179
-                $event->add('X-SABRE-BDAY', 'BDAY', [
180
-                    'X-SABRE-VCARD-UID' => $object->UID->getValue(),
181
-                    'X-SABRE-VCARD-FN'  => $object->FN->getValue(),
182
-                ]);
183
-            }
184
-
185
-        }
186
-
187
-        return $calendar;
188
-
189
-    }
171
+			// Add X-SABRE-BDAY property.
172
+			if ($unknownYear) {
173
+				$event->add('X-SABRE-BDAY', 'BDAY', [
174
+					'X-SABRE-VCARD-UID' => $object->UID->getValue(),
175
+					'X-SABRE-VCARD-FN'  => $object->FN->getValue(),
176
+					'X-SABRE-OMIT-YEAR' => self::DEFAULT_YEAR,
177
+				]);
178
+			} else {
179
+				$event->add('X-SABRE-BDAY', 'BDAY', [
180
+					'X-SABRE-VCARD-UID' => $object->UID->getValue(),
181
+					'X-SABRE-VCARD-FN'  => $object->FN->getValue(),
182
+				]);
183
+			}
184
+
185
+		}
186
+
187
+		return $calendar;
188
+
189
+	}
190 190
 
191 191
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/VObject/ITip/Broker.php 1 patch
Indentation   +948 added lines, -948 removed lines patch added patch discarded remove patch
@@ -37,953 +37,953 @@
 block discarded – undo
37 37
  */
38 38
 class Broker {
39 39
 
40
-    /**
41
-     * This setting determines whether the rules for the SCHEDULE-AGENT
42
-     * parameter should be followed.
43
-     *
44
-     * This is a parameter defined on ATTENDEE properties, introduced by RFC
45
-     * 6638. This parameter allows a caldav client to tell the server 'Don't do
46
-     * any scheduling operations'.
47
-     *
48
-     * If this setting is turned on, any attendees with SCHEDULE-AGENT set to
49
-     * CLIENT will be ignored. This is the desired behavior for a CalDAV
50
-     * server, but if you're writing an iTip application that doesn't deal with
51
-     * CalDAV, you may want to ignore this parameter.
52
-     *
53
-     * @var bool
54
-     */
55
-    public $scheduleAgentServerRules = true;
56
-
57
-    /**
58
-     * The broker will try during 'parseEvent' figure out whether the change
59
-     * was significant.
60
-     *
61
-     * It uses a few different ways to do this. One of these ways is seeing if
62
-     * certain properties changed values. This list of specified here.
63
-     *
64
-     * This list is taken from:
65
-     * * http://tools.ietf.org/html/rfc5546#section-2.1.4
66
-     *
67
-     * @var string[]
68
-     */
69
-    public $significantChangeProperties = [
70
-        'DTSTART',
71
-        'DTEND',
72
-        'DURATION',
73
-        'DUE',
74
-        'RRULE',
75
-        'RDATE',
76
-        'EXDATE',
77
-        'STATUS',
78
-    ];
79
-
80
-    /**
81
-     * This method is used to process an incoming itip message.
82
-     *
83
-     * Examples:
84
-     *
85
-     * 1. A user is an attendee to an event. The organizer sends an updated
86
-     * meeting using a new iTip message with METHOD:REQUEST. This function
87
-     * will process the message and update the attendee's event accordingly.
88
-     *
89
-     * 2. The organizer cancelled the event using METHOD:CANCEL. We will update
90
-     * the users event to state STATUS:CANCELLED.
91
-     *
92
-     * 3. An attendee sent a reply to an invite using METHOD:REPLY. We can
93
-     * update the organizers event to update the ATTENDEE with its correct
94
-     * PARTSTAT.
95
-     *
96
-     * The $existingObject is updated in-place. If there is no existing object
97
-     * (because it's a new invite for example) a new object will be created.
98
-     *
99
-     * If an existing object does not exist, and the method was CANCEL or
100
-     * REPLY, the message effectively gets ignored, and no 'existingObject'
101
-     * will be created.
102
-     *
103
-     * The updated $existingObject is also returned from this function.
104
-     *
105
-     * If the iTip message was not supported, we will always return false.
106
-     *
107
-     * @param Message $itipMessage
108
-     * @param VCalendar $existingObject
109
-     *
110
-     * @return VCalendar|null
111
-     */
112
-    public function processMessage(Message $itipMessage, VCalendar $existingObject = null) {
113
-
114
-        // We only support events at the moment.
115
-        if ($itipMessage->component !== 'VEVENT') {
116
-            return false;
117
-        }
118
-
119
-        switch ($itipMessage->method) {
120
-
121
-            case 'REQUEST' :
122
-                return $this->processMessageRequest($itipMessage, $existingObject);
123
-
124
-            case 'CANCEL' :
125
-                return $this->processMessageCancel($itipMessage, $existingObject);
126
-
127
-            case 'REPLY' :
128
-                return $this->processMessageReply($itipMessage, $existingObject);
129
-
130
-            default :
131
-                // Unsupported iTip message
132
-                return;
133
-
134
-        }
135
-
136
-        return $existingObject;
137
-
138
-    }
139
-
140
-    /**
141
-     * This function parses a VCALENDAR object and figure out if any messages
142
-     * need to be sent.
143
-     *
144
-     * A VCALENDAR object will be created from the perspective of either an
145
-     * attendee, or an organizer. You must pass a string identifying the
146
-     * current user, so we can figure out who in the list of attendees or the
147
-     * organizer we are sending this message on behalf of.
148
-     *
149
-     * It's possible to specify the current user as an array, in case the user
150
-     * has more than one identifying href (such as multiple emails).
151
-     *
152
-     * It $oldCalendar is specified, it is assumed that the operation is
153
-     * updating an existing event, which means that we need to look at the
154
-     * differences between events, and potentially send old attendees
155
-     * cancellations, and current attendees updates.
156
-     *
157
-     * If $calendar is null, but $oldCalendar is specified, we treat the
158
-     * operation as if the user has deleted an event. If the user was an
159
-     * organizer, this means that we need to send cancellation notices to
160
-     * people. If the user was an attendee, we need to make sure that the
161
-     * organizer gets the 'declined' message.
162
-     *
163
-     * @param VCalendar|string $calendar
164
-     * @param string|array $userHref
165
-     * @param VCalendar|string $oldCalendar
166
-     *
167
-     * @return array
168
-     */
169
-    public function parseEvent($calendar = null, $userHref, $oldCalendar = null) {
170
-
171
-        if ($oldCalendar) {
172
-            if (is_string($oldCalendar)) {
173
-                $oldCalendar = Reader::read($oldCalendar);
174
-            }
175
-            if (!isset($oldCalendar->VEVENT)) {
176
-                // We only support events at the moment
177
-                return [];
178
-            }
179
-
180
-            $oldEventInfo = $this->parseEventInfo($oldCalendar);
181
-        } else {
182
-            $oldEventInfo = [
183
-                'organizer'             => null,
184
-                'significantChangeHash' => '',
185
-                'attendees'             => [],
186
-            ];
187
-        }
188
-
189
-        $userHref = (array)$userHref;
190
-
191
-        if (!is_null($calendar)) {
192
-
193
-            if (is_string($calendar)) {
194
-                $calendar = Reader::read($calendar);
195
-            }
196
-            if (!isset($calendar->VEVENT)) {
197
-                // We only support events at the moment
198
-                return [];
199
-            }
200
-            $eventInfo = $this->parseEventInfo($calendar);
201
-            if (!$eventInfo['attendees'] && !$oldEventInfo['attendees']) {
202
-                // If there were no attendees on either side of the equation,
203
-                // we don't need to do anything.
204
-                return [];
205
-            }
206
-            if (!$eventInfo['organizer'] && !$oldEventInfo['organizer']) {
207
-                // There was no organizer before or after the change.
208
-                return [];
209
-            }
210
-
211
-            $baseCalendar = $calendar;
212
-
213
-            // If the new object didn't have an organizer, the organizer
214
-            // changed the object from a scheduling object to a non-scheduling
215
-            // object. We just copy the info from the old object.
216
-            if (!$eventInfo['organizer'] && $oldEventInfo['organizer']) {
217
-                $eventInfo['organizer'] = $oldEventInfo['organizer'];
218
-                $eventInfo['organizerName'] = $oldEventInfo['organizerName'];
219
-            }
220
-
221
-        } else {
222
-            // The calendar object got deleted, we need to process this as a
223
-            // cancellation / decline.
224
-            if (!$oldCalendar) {
225
-                // No old and no new calendar, there's no thing to do.
226
-                return [];
227
-            }
228
-
229
-            $eventInfo = $oldEventInfo;
230
-
231
-            if (in_array($eventInfo['organizer'], $userHref)) {
232
-                // This is an organizer deleting the event.
233
-                $eventInfo['attendees'] = [];
234
-                // Increasing the sequence, but only if the organizer deleted
235
-                // the event.
236
-                $eventInfo['sequence']++;
237
-            } else {
238
-                // This is an attendee deleting the event.
239
-                foreach ($eventInfo['attendees'] as $key => $attendee) {
240
-                    if (in_array($attendee['href'], $userHref)) {
241
-                        $eventInfo['attendees'][$key]['instances'] = ['master' =>
242
-                            ['id' => 'master', 'partstat' => 'DECLINED']
243
-                        ];
244
-                    }
245
-                }
246
-            }
247
-            $baseCalendar = $oldCalendar;
248
-
249
-        }
250
-
251
-        if (in_array($eventInfo['organizer'], $userHref)) {
252
-            return $this->parseEventForOrganizer($baseCalendar, $eventInfo, $oldEventInfo);
253
-        } elseif ($oldCalendar) {
254
-            // We need to figure out if the user is an attendee, but we're only
255
-            // doing so if there's an oldCalendar, because we only want to
256
-            // process updates, not creation of new events.
257
-            foreach ($eventInfo['attendees'] as $attendee) {
258
-                if (in_array($attendee['href'], $userHref)) {
259
-                    return $this->parseEventForAttendee($baseCalendar, $eventInfo, $oldEventInfo, $attendee['href']);
260
-                }
261
-            }
262
-        }
263
-        return [];
264
-
265
-    }
266
-
267
-    /**
268
-     * Processes incoming REQUEST messages.
269
-     *
270
-     * This is message from an organizer, and is either a new event
271
-     * invite, or an update to an existing one.
272
-     *
273
-     *
274
-     * @param Message $itipMessage
275
-     * @param VCalendar $existingObject
276
-     *
277
-     * @return VCalendar|null
278
-     */
279
-    protected function processMessageRequest(Message $itipMessage, VCalendar $existingObject = null) {
280
-
281
-        if (!$existingObject) {
282
-            // This is a new invite, and we're just going to copy over
283
-            // all the components from the invite.
284
-            $existingObject = new VCalendar();
285
-            foreach ($itipMessage->message->getComponents() as $component) {
286
-                $existingObject->add(clone $component);
287
-            }
288
-        } else {
289
-            // We need to update an existing object with all the new
290
-            // information. We can just remove all existing components
291
-            // and create new ones.
292
-            foreach ($existingObject->getComponents() as $component) {
293
-                $existingObject->remove($component);
294
-            }
295
-            foreach ($itipMessage->message->getComponents() as $component) {
296
-                $existingObject->add(clone $component);
297
-            }
298
-        }
299
-        return $existingObject;
300
-
301
-    }
302
-
303
-    /**
304
-     * Processes incoming CANCEL messages.
305
-     *
306
-     * This is a message from an organizer, and means that either an
307
-     * attendee got removed from an event, or an event got cancelled
308
-     * altogether.
309
-     *
310
-     * @param Message $itipMessage
311
-     * @param VCalendar $existingObject
312
-     *
313
-     * @return VCalendar|null
314
-     */
315
-    protected function processMessageCancel(Message $itipMessage, VCalendar $existingObject = null) {
316
-
317
-        if (!$existingObject) {
318
-            // The event didn't exist in the first place, so we're just
319
-            // ignoring this message.
320
-        } else {
321
-            foreach ($existingObject->VEVENT as $vevent) {
322
-                $vevent->STATUS = 'CANCELLED';
323
-                $vevent->SEQUENCE = $itipMessage->sequence;
324
-            }
325
-        }
326
-        return $existingObject;
327
-
328
-    }
329
-
330
-    /**
331
-     * Processes incoming REPLY messages.
332
-     *
333
-     * The message is a reply. This is for example an attendee telling
334
-     * an organizer he accepted the invite, or declined it.
335
-     *
336
-     * @param Message $itipMessage
337
-     * @param VCalendar $existingObject
338
-     *
339
-     * @return VCalendar|null
340
-     */
341
-    protected function processMessageReply(Message $itipMessage, VCalendar $existingObject = null) {
342
-
343
-        // A reply can only be processed based on an existing object.
344
-        // If the object is not available, the reply is ignored.
345
-        if (!$existingObject) {
346
-            return;
347
-        }
348
-        $instances = [];
349
-        $requestStatus = '2.0';
350
-
351
-        // Finding all the instances the attendee replied to.
352
-        foreach ($itipMessage->message->VEVENT as $vevent) {
353
-            $recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master';
354
-            $attendee = $vevent->ATTENDEE;
355
-            $instances[$recurId] = $attendee['PARTSTAT']->getValue();
356
-            if (isset($vevent->{'REQUEST-STATUS'})) {
357
-                $requestStatus = $vevent->{'REQUEST-STATUS'}->getValue();
358
-                list($requestStatus) = explode(';', $requestStatus);
359
-            }
360
-        }
361
-
362
-        // Now we need to loop through the original organizer event, to find
363
-        // all the instances where we have a reply for.
364
-        $masterObject = null;
365
-        foreach ($existingObject->VEVENT as $vevent) {
366
-            $recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master';
367
-            if ($recurId === 'master') {
368
-                $masterObject = $vevent;
369
-            }
370
-            if (isset($instances[$recurId])) {
371
-                $attendeeFound = false;
372
-                if (isset($vevent->ATTENDEE)) {
373
-                    foreach ($vevent->ATTENDEE as $attendee) {
374
-                        if ($attendee->getValue() === $itipMessage->sender) {
375
-                            $attendeeFound = true;
376
-                            $attendee['PARTSTAT'] = $instances[$recurId];
377
-                            $attendee['SCHEDULE-STATUS'] = $requestStatus;
378
-                            // Un-setting the RSVP status, because we now know
379
-                            // that the attendee already replied.
380
-                            unset($attendee['RSVP']);
381
-                            break;
382
-                        }
383
-                    }
384
-                }
385
-                if (!$attendeeFound) {
386
-                    // Adding a new attendee. The iTip documentation calls this
387
-                    // a party crasher.
388
-                    $attendee = $vevent->add('ATTENDEE', $itipMessage->sender, [
389
-                        'PARTSTAT' => $instances[$recurId]
390
-                    ]);
391
-                    if ($itipMessage->senderName) $attendee['CN'] = $itipMessage->senderName;
392
-                }
393
-                unset($instances[$recurId]);
394
-            }
395
-        }
396
-
397
-        if (!$masterObject) {
398
-            // No master object, we can't add new instances.
399
-            return;
400
-        }
401
-        // If we got replies to instances that did not exist in the
402
-        // original list, it means that new exceptions must be created.
403
-        foreach ($instances as $recurId => $partstat) {
404
-
405
-            $recurrenceIterator = new EventIterator($existingObject, $itipMessage->uid);
406
-            $found = false;
407
-            $iterations = 1000;
408
-            do {
409
-
410
-                $newObject = $recurrenceIterator->getEventObject();
411
-                $recurrenceIterator->next();
412
-
413
-                if (isset($newObject->{'RECURRENCE-ID'}) && $newObject->{'RECURRENCE-ID'}->getValue() === $recurId) {
414
-                    $found = true;
415
-                }
416
-                $iterations--;
417
-
418
-            } while ($recurrenceIterator->valid() && !$found && $iterations);
419
-
420
-            // Invalid recurrence id. Skipping this object.
421
-            if (!$found) continue;
422
-
423
-            unset(
424
-                $newObject->RRULE,
425
-                $newObject->EXDATE,
426
-                $newObject->RDATE
427
-            );
428
-            $attendeeFound = false;
429
-            if (isset($newObject->ATTENDEE)) {
430
-                foreach ($newObject->ATTENDEE as $attendee) {
431
-                    if ($attendee->getValue() === $itipMessage->sender) {
432
-                        $attendeeFound = true;
433
-                        $attendee['PARTSTAT'] = $partstat;
434
-                        break;
435
-                    }
436
-                }
437
-            }
438
-            if (!$attendeeFound) {
439
-                // Adding a new attendee
440
-                $attendee = $newObject->add('ATTENDEE', $itipMessage->sender, [
441
-                    'PARTSTAT' => $partstat
442
-                ]);
443
-                if ($itipMessage->senderName) {
444
-                    $attendee['CN'] = $itipMessage->senderName;
445
-                }
446
-            }
447
-            $existingObject->add($newObject);
448
-
449
-        }
450
-        return $existingObject;
451
-
452
-    }
453
-
454
-    /**
455
-     * This method is used in cases where an event got updated, and we
456
-     * potentially need to send emails to attendees to let them know of updates
457
-     * in the events.
458
-     *
459
-     * We will detect which attendees got added, which got removed and create
460
-     * specific messages for these situations.
461
-     *
462
-     * @param VCalendar $calendar
463
-     * @param array $eventInfo
464
-     * @param array $oldEventInfo
465
-     *
466
-     * @return array
467
-     */
468
-    protected function parseEventForOrganizer(VCalendar $calendar, array $eventInfo, array $oldEventInfo) {
469
-
470
-        // Merging attendee lists.
471
-        $attendees = [];
472
-        foreach ($oldEventInfo['attendees'] as $attendee) {
473
-            $attendees[$attendee['href']] = [
474
-                'href'         => $attendee['href'],
475
-                'oldInstances' => $attendee['instances'],
476
-                'newInstances' => [],
477
-                'name'         => $attendee['name'],
478
-                'forceSend'    => null,
479
-            ];
480
-        }
481
-        foreach ($eventInfo['attendees'] as $attendee) {
482
-            if (isset($attendees[$attendee['href']])) {
483
-                $attendees[$attendee['href']]['name'] = $attendee['name'];
484
-                $attendees[$attendee['href']]['newInstances'] = $attendee['instances'];
485
-                $attendees[$attendee['href']]['forceSend'] = $attendee['forceSend'];
486
-            } else {
487
-                $attendees[$attendee['href']] = [
488
-                    'href'         => $attendee['href'],
489
-                    'oldInstances' => [],
490
-                    'newInstances' => $attendee['instances'],
491
-                    'name'         => $attendee['name'],
492
-                    'forceSend'    => $attendee['forceSend'],
493
-                ];
494
-            }
495
-        }
496
-
497
-        $messages = [];
498
-
499
-        foreach ($attendees as $attendee) {
500
-
501
-            // An organizer can also be an attendee. We should not generate any
502
-            // messages for those.
503
-            if ($attendee['href'] === $eventInfo['organizer']) {
504
-                continue;
505
-            }
506
-
507
-            $message = new Message();
508
-            $message->uid = $eventInfo['uid'];
509
-            $message->component = 'VEVENT';
510
-            $message->sequence = $eventInfo['sequence'];
511
-            $message->sender = $eventInfo['organizer'];
512
-            $message->senderName = $eventInfo['organizerName'];
513
-            $message->recipient = $attendee['href'];
514
-            $message->recipientName = $attendee['name'];
515
-
516
-            if (!$attendee['newInstances']) {
517
-
518
-                // If there are no instances the attendee is a part of, it
519
-                // means the attendee was removed and we need to send him a
520
-                // CANCEL.
521
-                $message->method = 'CANCEL';
522
-
523
-                // Creating the new iCalendar body.
524
-                $icalMsg = new VCalendar();
525
-                $icalMsg->METHOD = $message->method;
526
-                $event = $icalMsg->add('VEVENT', [
527
-                    'UID'      => $message->uid,
528
-                    'SEQUENCE' => $message->sequence,
529
-                ]);
530
-                if (isset($calendar->VEVENT->SUMMARY)) {
531
-                    $event->add('SUMMARY', $calendar->VEVENT->SUMMARY->getValue());
532
-                }
533
-                $event->add(clone $calendar->VEVENT->DTSTART);
534
-                if (isset($calendar->VEVENT->DTEND)) {
535
-                    $event->add(clone $calendar->VEVENT->DTEND);
536
-                } elseif (isset($calendar->VEVENT->DURATION)) {
537
-                    $event->add(clone $calendar->VEVENT->DURATION);
538
-                }
539
-                $org = $event->add('ORGANIZER', $eventInfo['organizer']);
540
-                if ($eventInfo['organizerName']) $org['CN'] = $eventInfo['organizerName'];
541
-                $event->add('ATTENDEE', $attendee['href'], [
542
-                    'CN' => $attendee['name'],
543
-                ]);
544
-                $message->significantChange = true;
545
-
546
-            } else {
547
-
548
-                // The attendee gets the updated event body
549
-                $message->method = 'REQUEST';
550
-
551
-                // Creating the new iCalendar body.
552
-                $icalMsg = new VCalendar();
553
-                $icalMsg->METHOD = $message->method;
554
-
555
-                foreach ($calendar->select('VTIMEZONE') as $timezone) {
556
-                    $icalMsg->add(clone $timezone);
557
-                }
558
-
559
-                // We need to find out that this change is significant. If it's
560
-                // not, systems may opt to not send messages.
561
-                //
562
-                // We do this based on the 'significantChangeHash' which is
563
-                // some value that changes if there's a certain set of
564
-                // properties changed in the event, or simply if there's a
565
-                // difference in instances that the attendee is invited to.
566
-
567
-                $message->significantChange =
568
-                    $attendee['forceSend'] === 'REQUEST' ||
569
-                    array_keys($attendee['oldInstances']) != array_keys($attendee['newInstances']) ||
570
-                    $oldEventInfo['significantChangeHash'] !== $eventInfo['significantChangeHash'];
571
-
572
-                foreach ($attendee['newInstances'] as $instanceId => $instanceInfo) {
573
-
574
-                    $currentEvent = clone $eventInfo['instances'][$instanceId];
575
-                    if ($instanceId === 'master') {
576
-
577
-                        // We need to find a list of events that the attendee
578
-                        // is not a part of to add to the list of exceptions.
579
-                        $exceptions = [];
580
-                        foreach ($eventInfo['instances'] as $instanceId => $vevent) {
581
-                            if (!isset($attendee['newInstances'][$instanceId])) {
582
-                                $exceptions[] = $instanceId;
583
-                            }
584
-                        }
585
-
586
-                        // If there were exceptions, we need to add it to an
587
-                        // existing EXDATE property, if it exists.
588
-                        if ($exceptions) {
589
-                            if (isset($currentEvent->EXDATE)) {
590
-                                $currentEvent->EXDATE->setParts(array_merge(
591
-                                    $currentEvent->EXDATE->getParts(),
592
-                                    $exceptions
593
-                                ));
594
-                            } else {
595
-                                $currentEvent->EXDATE = $exceptions;
596
-                            }
597
-                        }
598
-
599
-                        // Cleaning up any scheduling information that
600
-                        // shouldn't be sent along.
601
-                        unset($currentEvent->ORGANIZER['SCHEDULE-FORCE-SEND']);
602
-                        unset($currentEvent->ORGANIZER['SCHEDULE-STATUS']);
603
-
604
-                        foreach ($currentEvent->ATTENDEE as $attendee) {
605
-                            unset($attendee['SCHEDULE-FORCE-SEND']);
606
-                            unset($attendee['SCHEDULE-STATUS']);
607
-
608
-                            // We're adding PARTSTAT=NEEDS-ACTION to ensure that
609
-                            // iOS shows an "Inbox Item"
610
-                            if (!isset($attendee['PARTSTAT'])) {
611
-                                $attendee['PARTSTAT'] = 'NEEDS-ACTION';
612
-                            }
613
-
614
-                        }
615
-
616
-                    }
617
-
618
-                    $icalMsg->add($currentEvent);
619
-
620
-                }
621
-
622
-            }
623
-
624
-            $message->message = $icalMsg;
625
-            $messages[] = $message;
626
-
627
-        }
628
-
629
-        return $messages;
630
-
631
-    }
632
-
633
-    /**
634
-     * Parse an event update for an attendee.
635
-     *
636
-     * This function figures out if we need to send a reply to an organizer.
637
-     *
638
-     * @param VCalendar $calendar
639
-     * @param array $eventInfo
640
-     * @param array $oldEventInfo
641
-     * @param string $attendee
642
-     *
643
-     * @return Message[]
644
-     */
645
-    protected function parseEventForAttendee(VCalendar $calendar, array $eventInfo, array $oldEventInfo, $attendee) {
646
-
647
-        if ($this->scheduleAgentServerRules && $eventInfo['organizerScheduleAgent'] === 'CLIENT') {
648
-            return [];
649
-        }
650
-
651
-        // Don't bother generating messages for events that have already been
652
-        // cancelled.
653
-        if ($eventInfo['status'] === 'CANCELLED') {
654
-            return [];
655
-        }
656
-
657
-        $oldInstances = !empty($oldEventInfo['attendees'][$attendee]['instances']) ?
658
-            $oldEventInfo['attendees'][$attendee]['instances'] :
659
-            [];
660
-
661
-        $instances = [];
662
-        foreach ($oldInstances as $instance) {
663
-
664
-            $instances[$instance['id']] = [
665
-                'id'        => $instance['id'],
666
-                'oldstatus' => $instance['partstat'],
667
-                'newstatus' => null,
668
-            ];
669
-
670
-        }
671
-        foreach ($eventInfo['attendees'][$attendee]['instances'] as $instance) {
672
-
673
-            if (isset($instances[$instance['id']])) {
674
-                $instances[$instance['id']]['newstatus'] = $instance['partstat'];
675
-            } else {
676
-                $instances[$instance['id']] = [
677
-                    'id'        => $instance['id'],
678
-                    'oldstatus' => null,
679
-                    'newstatus' => $instance['partstat'],
680
-                ];
681
-            }
682
-
683
-        }
684
-
685
-        // We need to also look for differences in EXDATE. If there are new
686
-        // items in EXDATE, it means that an attendee deleted instances of an
687
-        // event, which means we need to send DECLINED specifically for those
688
-        // instances.
689
-        // We only need to do that though, if the master event is not declined.
690
-        if (isset($instances['master']) && $instances['master']['newstatus'] !== 'DECLINED') {
691
-            foreach ($eventInfo['exdate'] as $exDate) {
692
-
693
-                if (!in_array($exDate, $oldEventInfo['exdate'])) {
694
-                    if (isset($instances[$exDate])) {
695
-                        $instances[$exDate]['newstatus'] = 'DECLINED';
696
-                    } else {
697
-                        $instances[$exDate] = [
698
-                            'id'        => $exDate,
699
-                            'oldstatus' => null,
700
-                            'newstatus' => 'DECLINED',
701
-                        ];
702
-                    }
703
-                }
704
-
705
-            }
706
-        }
707
-
708
-        // Gathering a few extra properties for each instance.
709
-        foreach ($instances as $recurId => $instanceInfo) {
710
-
711
-            if (isset($eventInfo['instances'][$recurId])) {
712
-                $instances[$recurId]['dtstart'] = clone $eventInfo['instances'][$recurId]->DTSTART;
713
-            } else {
714
-                $instances[$recurId]['dtstart'] = $recurId;
715
-            }
716
-
717
-        }
718
-
719
-        $message = new Message();
720
-        $message->uid = $eventInfo['uid'];
721
-        $message->method = 'REPLY';
722
-        $message->component = 'VEVENT';
723
-        $message->sequence = $eventInfo['sequence'];
724
-        $message->sender = $attendee;
725
-        $message->senderName = $eventInfo['attendees'][$attendee]['name'];
726
-        $message->recipient = $eventInfo['organizer'];
727
-        $message->recipientName = $eventInfo['organizerName'];
728
-
729
-        $icalMsg = new VCalendar();
730
-        $icalMsg->METHOD = 'REPLY';
731
-
732
-        $hasReply = false;
733
-
734
-        foreach ($instances as $instance) {
735
-
736
-            if ($instance['oldstatus'] == $instance['newstatus'] && $eventInfo['organizerForceSend'] !== 'REPLY') {
737
-                // Skip
738
-                continue;
739
-            }
740
-
741
-            $event = $icalMsg->add('VEVENT', [
742
-                'UID'      => $message->uid,
743
-                'SEQUENCE' => $message->sequence,
744
-            ]);
745
-            $summary = isset($calendar->VEVENT->SUMMARY) ? $calendar->VEVENT->SUMMARY->getValue() : '';
746
-            // Adding properties from the correct source instance
747
-            if (isset($eventInfo['instances'][$instance['id']])) {
748
-                $instanceObj = $eventInfo['instances'][$instance['id']];
749
-                $event->add(clone $instanceObj->DTSTART);
750
-                if (isset($instanceObj->DTEND)) {
751
-                    $event->add(clone $instanceObj->DTEND);
752
-                } elseif (isset($instanceObj->DURATION)) {
753
-                    $event->add(clone $instanceObj->DURATION);
754
-                }
755
-                if (isset($instanceObj->SUMMARY)) {
756
-                    $event->add('SUMMARY', $instanceObj->SUMMARY->getValue());
757
-                } elseif ($summary) {
758
-                    $event->add('SUMMARY', $summary);
759
-                }
760
-            } else {
761
-                // This branch of the code is reached, when a reply is
762
-                // generated for an instance of a recurring event, through the
763
-                // fact that the instance has disappeared by showing up in
764
-                // EXDATE
765
-                $dt = DateTimeParser::parse($instance['id'], $eventInfo['timezone']);
766
-                // Treat is as a DATE field
767
-                if (strlen($instance['id']) <= 8) {
768
-                    $event->add('DTSTART', $dt, ['VALUE' => 'DATE']);
769
-                } else {
770
-                    $event->add('DTSTART', $dt);
771
-                }
772
-                if ($summary) {
773
-                    $event->add('SUMMARY', $summary);
774
-                }
775
-            }
776
-            if ($instance['id'] !== 'master') {
777
-                $dt = DateTimeParser::parse($instance['id'], $eventInfo['timezone']);
778
-                // Treat is as a DATE field
779
-                if (strlen($instance['id']) <= 8) {
780
-                    $event->add('RECURRENCE-ID', $dt, ['VALUE' => 'DATE']);
781
-                } else {
782
-                    $event->add('RECURRENCE-ID', $dt);
783
-                }
784
-            }
785
-            $organizer = $event->add('ORGANIZER', $message->recipient);
786
-            if ($message->recipientName) {
787
-                $organizer['CN'] = $message->recipientName;
788
-            }
789
-            $attendee = $event->add('ATTENDEE', $message->sender, [
790
-                'PARTSTAT' => $instance['newstatus']
791
-            ]);
792
-            if ($message->senderName) {
793
-                $attendee['CN'] = $message->senderName;
794
-            }
795
-            $hasReply = true;
796
-
797
-        }
798
-
799
-        if ($hasReply) {
800
-            $message->message = $icalMsg;
801
-            return [$message];
802
-        } else {
803
-            return [];
804
-        }
805
-
806
-    }
807
-
808
-    /**
809
-     * Returns attendee information and information about instances of an
810
-     * event.
811
-     *
812
-     * Returns an array with the following keys:
813
-     *
814
-     * 1. uid
815
-     * 2. organizer
816
-     * 3. organizerName
817
-     * 4. organizerScheduleAgent
818
-     * 5. organizerForceSend
819
-     * 6. instances
820
-     * 7. attendees
821
-     * 8. sequence
822
-     * 9. exdate
823
-     * 10. timezone - strictly the timezone on which the recurrence rule is
824
-     *                based on.
825
-     * 11. significantChangeHash
826
-     * 12. status
827
-     * @param VCalendar $calendar
828
-     *
829
-     * @return array
830
-     */
831
-    protected function parseEventInfo(VCalendar $calendar = null) {
832
-
833
-        $uid = null;
834
-        $organizer = null;
835
-        $organizerName = null;
836
-        $organizerForceSend = null;
837
-        $sequence = null;
838
-        $timezone = null;
839
-        $status = null;
840
-        $organizerScheduleAgent = 'SERVER';
841
-
842
-        $significantChangeHash = '';
843
-
844
-        // Now we need to collect a list of attendees, and which instances they
845
-        // are a part of.
846
-        $attendees = [];
847
-
848
-        $instances = [];
849
-        $exdate = [];
850
-
851
-        foreach ($calendar->VEVENT as $vevent) {
852
-
853
-            if (is_null($uid)) {
854
-                $uid = $vevent->UID->getValue();
855
-            } else {
856
-                if ($uid !== $vevent->UID->getValue()) {
857
-                    throw new ITipException('If a calendar contained more than one event, they must have the same UID.');
858
-                }
859
-            }
860
-
861
-            if (!isset($vevent->DTSTART)) {
862
-                throw new ITipException('An event MUST have a DTSTART property.');
863
-            }
864
-
865
-            if (isset($vevent->ORGANIZER)) {
866
-                if (is_null($organizer)) {
867
-                    $organizer = $vevent->ORGANIZER->getNormalizedValue();
868
-                    $organizerName = isset($vevent->ORGANIZER['CN']) ? $vevent->ORGANIZER['CN'] : null;
869
-                } else {
870
-                    if ($organizer !== $vevent->ORGANIZER->getNormalizedValue()) {
871
-                        throw new SameOrganizerForAllComponentsException('Every instance of the event must have the same organizer.');
872
-                    }
873
-                }
874
-                $organizerForceSend =
875
-                    isset($vevent->ORGANIZER['SCHEDULE-FORCE-SEND']) ?
876
-                    strtoupper($vevent->ORGANIZER['SCHEDULE-FORCE-SEND']) :
877
-                    null;
878
-                $organizerScheduleAgent =
879
-                    isset($vevent->ORGANIZER['SCHEDULE-AGENT']) ?
880
-                    strtoupper((string)$vevent->ORGANIZER['SCHEDULE-AGENT']) :
881
-                    'SERVER';
882
-            }
883
-            if (is_null($sequence) && isset($vevent->SEQUENCE)) {
884
-                $sequence = $vevent->SEQUENCE->getValue();
885
-            }
886
-            if (isset($vevent->EXDATE)) {
887
-                foreach ($vevent->select('EXDATE') as $val) {
888
-                    $exdate = array_merge($exdate, $val->getParts());
889
-                }
890
-                sort($exdate);
891
-            }
892
-            if (isset($vevent->STATUS)) {
893
-                $status = strtoupper($vevent->STATUS->getValue());
894
-            }
895
-
896
-            $recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master';
897
-            if (is_null($timezone)) {
898
-                if ($recurId === 'master') {
899
-                    $timezone = $vevent->DTSTART->getDateTime()->getTimeZone();
900
-                } else {
901
-                    $timezone = $vevent->{'RECURRENCE-ID'}->getDateTime()->getTimeZone();
902
-                }
903
-            }
904
-            if (isset($vevent->ATTENDEE)) {
905
-                foreach ($vevent->ATTENDEE as $attendee) {
906
-
907
-                    if ($this->scheduleAgentServerRules &&
908
-                        isset($attendee['SCHEDULE-AGENT']) &&
909
-                        strtoupper($attendee['SCHEDULE-AGENT']->getValue()) === 'CLIENT'
910
-                    ) {
911
-                        continue;
912
-                    }
913
-                    $partStat =
914
-                        isset($attendee['PARTSTAT']) ?
915
-                        strtoupper($attendee['PARTSTAT']) :
916
-                        'NEEDS-ACTION';
917
-
918
-                    $forceSend =
919
-                        isset($attendee['SCHEDULE-FORCE-SEND']) ?
920
-                        strtoupper($attendee['SCHEDULE-FORCE-SEND']) :
921
-                        null;
922
-
923
-
924
-                    if (isset($attendees[$attendee->getNormalizedValue()])) {
925
-                        $attendees[$attendee->getNormalizedValue()]['instances'][$recurId] = [
926
-                            'id'         => $recurId,
927
-                            'partstat'   => $partStat,
928
-                            'force-send' => $forceSend,
929
-                        ];
930
-                    } else {
931
-                        $attendees[$attendee->getNormalizedValue()] = [
932
-                            'href'      => $attendee->getNormalizedValue(),
933
-                            'instances' => [
934
-                                $recurId => [
935
-                                    'id'       => $recurId,
936
-                                    'partstat' => $partStat,
937
-                                ],
938
-                            ],
939
-                            'name'      => isset($attendee['CN']) ? (string)$attendee['CN'] : null,
940
-                            'forceSend' => $forceSend,
941
-                        ];
942
-                    }
943
-
944
-                }
945
-                $instances[$recurId] = $vevent;
946
-
947
-            }
948
-
949
-            foreach ($this->significantChangeProperties as $prop) {
950
-                if (isset($vevent->$prop)) {
951
-                    $propertyValues = $vevent->select($prop);
952
-
953
-                    $significantChangeHash .= $prop . ':';
954
-
955
-                    if ($prop === 'EXDATE') {
956
-
957
-                        $significantChangeHash .= implode(',', $exdate) . ';';
958
-
959
-                    } else {
960
-
961
-                        foreach ($propertyValues as $val) {
962
-                            $significantChangeHash .= $val->getValue() . ';';
963
-                        }
964
-
965
-                    }
966
-                }
967
-            }
968
-
969
-        }
970
-        $significantChangeHash = md5($significantChangeHash);
971
-
972
-        return compact(
973
-            'uid',
974
-            'organizer',
975
-            'organizerName',
976
-            'organizerScheduleAgent',
977
-            'organizerForceSend',
978
-            'instances',
979
-            'attendees',
980
-            'sequence',
981
-            'exdate',
982
-            'timezone',
983
-            'significantChangeHash',
984
-            'status'
985
-        );
986
-
987
-    }
40
+	/**
41
+	 * This setting determines whether the rules for the SCHEDULE-AGENT
42
+	 * parameter should be followed.
43
+	 *
44
+	 * This is a parameter defined on ATTENDEE properties, introduced by RFC
45
+	 * 6638. This parameter allows a caldav client to tell the server 'Don't do
46
+	 * any scheduling operations'.
47
+	 *
48
+	 * If this setting is turned on, any attendees with SCHEDULE-AGENT set to
49
+	 * CLIENT will be ignored. This is the desired behavior for a CalDAV
50
+	 * server, but if you're writing an iTip application that doesn't deal with
51
+	 * CalDAV, you may want to ignore this parameter.
52
+	 *
53
+	 * @var bool
54
+	 */
55
+	public $scheduleAgentServerRules = true;
56
+
57
+	/**
58
+	 * The broker will try during 'parseEvent' figure out whether the change
59
+	 * was significant.
60
+	 *
61
+	 * It uses a few different ways to do this. One of these ways is seeing if
62
+	 * certain properties changed values. This list of specified here.
63
+	 *
64
+	 * This list is taken from:
65
+	 * * http://tools.ietf.org/html/rfc5546#section-2.1.4
66
+	 *
67
+	 * @var string[]
68
+	 */
69
+	public $significantChangeProperties = [
70
+		'DTSTART',
71
+		'DTEND',
72
+		'DURATION',
73
+		'DUE',
74
+		'RRULE',
75
+		'RDATE',
76
+		'EXDATE',
77
+		'STATUS',
78
+	];
79
+
80
+	/**
81
+	 * This method is used to process an incoming itip message.
82
+	 *
83
+	 * Examples:
84
+	 *
85
+	 * 1. A user is an attendee to an event. The organizer sends an updated
86
+	 * meeting using a new iTip message with METHOD:REQUEST. This function
87
+	 * will process the message and update the attendee's event accordingly.
88
+	 *
89
+	 * 2. The organizer cancelled the event using METHOD:CANCEL. We will update
90
+	 * the users event to state STATUS:CANCELLED.
91
+	 *
92
+	 * 3. An attendee sent a reply to an invite using METHOD:REPLY. We can
93
+	 * update the organizers event to update the ATTENDEE with its correct
94
+	 * PARTSTAT.
95
+	 *
96
+	 * The $existingObject is updated in-place. If there is no existing object
97
+	 * (because it's a new invite for example) a new object will be created.
98
+	 *
99
+	 * If an existing object does not exist, and the method was CANCEL or
100
+	 * REPLY, the message effectively gets ignored, and no 'existingObject'
101
+	 * will be created.
102
+	 *
103
+	 * The updated $existingObject is also returned from this function.
104
+	 *
105
+	 * If the iTip message was not supported, we will always return false.
106
+	 *
107
+	 * @param Message $itipMessage
108
+	 * @param VCalendar $existingObject
109
+	 *
110
+	 * @return VCalendar|null
111
+	 */
112
+	public function processMessage(Message $itipMessage, VCalendar $existingObject = null) {
113
+
114
+		// We only support events at the moment.
115
+		if ($itipMessage->component !== 'VEVENT') {
116
+			return false;
117
+		}
118
+
119
+		switch ($itipMessage->method) {
120
+
121
+			case 'REQUEST' :
122
+				return $this->processMessageRequest($itipMessage, $existingObject);
123
+
124
+			case 'CANCEL' :
125
+				return $this->processMessageCancel($itipMessage, $existingObject);
126
+
127
+			case 'REPLY' :
128
+				return $this->processMessageReply($itipMessage, $existingObject);
129
+
130
+			default :
131
+				// Unsupported iTip message
132
+				return;
133
+
134
+		}
135
+
136
+		return $existingObject;
137
+
138
+	}
139
+
140
+	/**
141
+	 * This function parses a VCALENDAR object and figure out if any messages
142
+	 * need to be sent.
143
+	 *
144
+	 * A VCALENDAR object will be created from the perspective of either an
145
+	 * attendee, or an organizer. You must pass a string identifying the
146
+	 * current user, so we can figure out who in the list of attendees or the
147
+	 * organizer we are sending this message on behalf of.
148
+	 *
149
+	 * It's possible to specify the current user as an array, in case the user
150
+	 * has more than one identifying href (such as multiple emails).
151
+	 *
152
+	 * It $oldCalendar is specified, it is assumed that the operation is
153
+	 * updating an existing event, which means that we need to look at the
154
+	 * differences between events, and potentially send old attendees
155
+	 * cancellations, and current attendees updates.
156
+	 *
157
+	 * If $calendar is null, but $oldCalendar is specified, we treat the
158
+	 * operation as if the user has deleted an event. If the user was an
159
+	 * organizer, this means that we need to send cancellation notices to
160
+	 * people. If the user was an attendee, we need to make sure that the
161
+	 * organizer gets the 'declined' message.
162
+	 *
163
+	 * @param VCalendar|string $calendar
164
+	 * @param string|array $userHref
165
+	 * @param VCalendar|string $oldCalendar
166
+	 *
167
+	 * @return array
168
+	 */
169
+	public function parseEvent($calendar = null, $userHref, $oldCalendar = null) {
170
+
171
+		if ($oldCalendar) {
172
+			if (is_string($oldCalendar)) {
173
+				$oldCalendar = Reader::read($oldCalendar);
174
+			}
175
+			if (!isset($oldCalendar->VEVENT)) {
176
+				// We only support events at the moment
177
+				return [];
178
+			}
179
+
180
+			$oldEventInfo = $this->parseEventInfo($oldCalendar);
181
+		} else {
182
+			$oldEventInfo = [
183
+				'organizer'             => null,
184
+				'significantChangeHash' => '',
185
+				'attendees'             => [],
186
+			];
187
+		}
188
+
189
+		$userHref = (array)$userHref;
190
+
191
+		if (!is_null($calendar)) {
192
+
193
+			if (is_string($calendar)) {
194
+				$calendar = Reader::read($calendar);
195
+			}
196
+			if (!isset($calendar->VEVENT)) {
197
+				// We only support events at the moment
198
+				return [];
199
+			}
200
+			$eventInfo = $this->parseEventInfo($calendar);
201
+			if (!$eventInfo['attendees'] && !$oldEventInfo['attendees']) {
202
+				// If there were no attendees on either side of the equation,
203
+				// we don't need to do anything.
204
+				return [];
205
+			}
206
+			if (!$eventInfo['organizer'] && !$oldEventInfo['organizer']) {
207
+				// There was no organizer before or after the change.
208
+				return [];
209
+			}
210
+
211
+			$baseCalendar = $calendar;
212
+
213
+			// If the new object didn't have an organizer, the organizer
214
+			// changed the object from a scheduling object to a non-scheduling
215
+			// object. We just copy the info from the old object.
216
+			if (!$eventInfo['organizer'] && $oldEventInfo['organizer']) {
217
+				$eventInfo['organizer'] = $oldEventInfo['organizer'];
218
+				$eventInfo['organizerName'] = $oldEventInfo['organizerName'];
219
+			}
220
+
221
+		} else {
222
+			// The calendar object got deleted, we need to process this as a
223
+			// cancellation / decline.
224
+			if (!$oldCalendar) {
225
+				// No old and no new calendar, there's no thing to do.
226
+				return [];
227
+			}
228
+
229
+			$eventInfo = $oldEventInfo;
230
+
231
+			if (in_array($eventInfo['organizer'], $userHref)) {
232
+				// This is an organizer deleting the event.
233
+				$eventInfo['attendees'] = [];
234
+				// Increasing the sequence, but only if the organizer deleted
235
+				// the event.
236
+				$eventInfo['sequence']++;
237
+			} else {
238
+				// This is an attendee deleting the event.
239
+				foreach ($eventInfo['attendees'] as $key => $attendee) {
240
+					if (in_array($attendee['href'], $userHref)) {
241
+						$eventInfo['attendees'][$key]['instances'] = ['master' =>
242
+							['id' => 'master', 'partstat' => 'DECLINED']
243
+						];
244
+					}
245
+				}
246
+			}
247
+			$baseCalendar = $oldCalendar;
248
+
249
+		}
250
+
251
+		if (in_array($eventInfo['organizer'], $userHref)) {
252
+			return $this->parseEventForOrganizer($baseCalendar, $eventInfo, $oldEventInfo);
253
+		} elseif ($oldCalendar) {
254
+			// We need to figure out if the user is an attendee, but we're only
255
+			// doing so if there's an oldCalendar, because we only want to
256
+			// process updates, not creation of new events.
257
+			foreach ($eventInfo['attendees'] as $attendee) {
258
+				if (in_array($attendee['href'], $userHref)) {
259
+					return $this->parseEventForAttendee($baseCalendar, $eventInfo, $oldEventInfo, $attendee['href']);
260
+				}
261
+			}
262
+		}
263
+		return [];
264
+
265
+	}
266
+
267
+	/**
268
+	 * Processes incoming REQUEST messages.
269
+	 *
270
+	 * This is message from an organizer, and is either a new event
271
+	 * invite, or an update to an existing one.
272
+	 *
273
+	 *
274
+	 * @param Message $itipMessage
275
+	 * @param VCalendar $existingObject
276
+	 *
277
+	 * @return VCalendar|null
278
+	 */
279
+	protected function processMessageRequest(Message $itipMessage, VCalendar $existingObject = null) {
280
+
281
+		if (!$existingObject) {
282
+			// This is a new invite, and we're just going to copy over
283
+			// all the components from the invite.
284
+			$existingObject = new VCalendar();
285
+			foreach ($itipMessage->message->getComponents() as $component) {
286
+				$existingObject->add(clone $component);
287
+			}
288
+		} else {
289
+			// We need to update an existing object with all the new
290
+			// information. We can just remove all existing components
291
+			// and create new ones.
292
+			foreach ($existingObject->getComponents() as $component) {
293
+				$existingObject->remove($component);
294
+			}
295
+			foreach ($itipMessage->message->getComponents() as $component) {
296
+				$existingObject->add(clone $component);
297
+			}
298
+		}
299
+		return $existingObject;
300
+
301
+	}
302
+
303
+	/**
304
+	 * Processes incoming CANCEL messages.
305
+	 *
306
+	 * This is a message from an organizer, and means that either an
307
+	 * attendee got removed from an event, or an event got cancelled
308
+	 * altogether.
309
+	 *
310
+	 * @param Message $itipMessage
311
+	 * @param VCalendar $existingObject
312
+	 *
313
+	 * @return VCalendar|null
314
+	 */
315
+	protected function processMessageCancel(Message $itipMessage, VCalendar $existingObject = null) {
316
+
317
+		if (!$existingObject) {
318
+			// The event didn't exist in the first place, so we're just
319
+			// ignoring this message.
320
+		} else {
321
+			foreach ($existingObject->VEVENT as $vevent) {
322
+				$vevent->STATUS = 'CANCELLED';
323
+				$vevent->SEQUENCE = $itipMessage->sequence;
324
+			}
325
+		}
326
+		return $existingObject;
327
+
328
+	}
329
+
330
+	/**
331
+	 * Processes incoming REPLY messages.
332
+	 *
333
+	 * The message is a reply. This is for example an attendee telling
334
+	 * an organizer he accepted the invite, or declined it.
335
+	 *
336
+	 * @param Message $itipMessage
337
+	 * @param VCalendar $existingObject
338
+	 *
339
+	 * @return VCalendar|null
340
+	 */
341
+	protected function processMessageReply(Message $itipMessage, VCalendar $existingObject = null) {
342
+
343
+		// A reply can only be processed based on an existing object.
344
+		// If the object is not available, the reply is ignored.
345
+		if (!$existingObject) {
346
+			return;
347
+		}
348
+		$instances = [];
349
+		$requestStatus = '2.0';
350
+
351
+		// Finding all the instances the attendee replied to.
352
+		foreach ($itipMessage->message->VEVENT as $vevent) {
353
+			$recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master';
354
+			$attendee = $vevent->ATTENDEE;
355
+			$instances[$recurId] = $attendee['PARTSTAT']->getValue();
356
+			if (isset($vevent->{'REQUEST-STATUS'})) {
357
+				$requestStatus = $vevent->{'REQUEST-STATUS'}->getValue();
358
+				list($requestStatus) = explode(';', $requestStatus);
359
+			}
360
+		}
361
+
362
+		// Now we need to loop through the original organizer event, to find
363
+		// all the instances where we have a reply for.
364
+		$masterObject = null;
365
+		foreach ($existingObject->VEVENT as $vevent) {
366
+			$recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master';
367
+			if ($recurId === 'master') {
368
+				$masterObject = $vevent;
369
+			}
370
+			if (isset($instances[$recurId])) {
371
+				$attendeeFound = false;
372
+				if (isset($vevent->ATTENDEE)) {
373
+					foreach ($vevent->ATTENDEE as $attendee) {
374
+						if ($attendee->getValue() === $itipMessage->sender) {
375
+							$attendeeFound = true;
376
+							$attendee['PARTSTAT'] = $instances[$recurId];
377
+							$attendee['SCHEDULE-STATUS'] = $requestStatus;
378
+							// Un-setting the RSVP status, because we now know
379
+							// that the attendee already replied.
380
+							unset($attendee['RSVP']);
381
+							break;
382
+						}
383
+					}
384
+				}
385
+				if (!$attendeeFound) {
386
+					// Adding a new attendee. The iTip documentation calls this
387
+					// a party crasher.
388
+					$attendee = $vevent->add('ATTENDEE', $itipMessage->sender, [
389
+						'PARTSTAT' => $instances[$recurId]
390
+					]);
391
+					if ($itipMessage->senderName) $attendee['CN'] = $itipMessage->senderName;
392
+				}
393
+				unset($instances[$recurId]);
394
+			}
395
+		}
396
+
397
+		if (!$masterObject) {
398
+			// No master object, we can't add new instances.
399
+			return;
400
+		}
401
+		// If we got replies to instances that did not exist in the
402
+		// original list, it means that new exceptions must be created.
403
+		foreach ($instances as $recurId => $partstat) {
404
+
405
+			$recurrenceIterator = new EventIterator($existingObject, $itipMessage->uid);
406
+			$found = false;
407
+			$iterations = 1000;
408
+			do {
409
+
410
+				$newObject = $recurrenceIterator->getEventObject();
411
+				$recurrenceIterator->next();
412
+
413
+				if (isset($newObject->{'RECURRENCE-ID'}) && $newObject->{'RECURRENCE-ID'}->getValue() === $recurId) {
414
+					$found = true;
415
+				}
416
+				$iterations--;
417
+
418
+			} while ($recurrenceIterator->valid() && !$found && $iterations);
419
+
420
+			// Invalid recurrence id. Skipping this object.
421
+			if (!$found) continue;
422
+
423
+			unset(
424
+				$newObject->RRULE,
425
+				$newObject->EXDATE,
426
+				$newObject->RDATE
427
+			);
428
+			$attendeeFound = false;
429
+			if (isset($newObject->ATTENDEE)) {
430
+				foreach ($newObject->ATTENDEE as $attendee) {
431
+					if ($attendee->getValue() === $itipMessage->sender) {
432
+						$attendeeFound = true;
433
+						$attendee['PARTSTAT'] = $partstat;
434
+						break;
435
+					}
436
+				}
437
+			}
438
+			if (!$attendeeFound) {
439
+				// Adding a new attendee
440
+				$attendee = $newObject->add('ATTENDEE', $itipMessage->sender, [
441
+					'PARTSTAT' => $partstat
442
+				]);
443
+				if ($itipMessage->senderName) {
444
+					$attendee['CN'] = $itipMessage->senderName;
445
+				}
446
+			}
447
+			$existingObject->add($newObject);
448
+
449
+		}
450
+		return $existingObject;
451
+
452
+	}
453
+
454
+	/**
455
+	 * This method is used in cases where an event got updated, and we
456
+	 * potentially need to send emails to attendees to let them know of updates
457
+	 * in the events.
458
+	 *
459
+	 * We will detect which attendees got added, which got removed and create
460
+	 * specific messages for these situations.
461
+	 *
462
+	 * @param VCalendar $calendar
463
+	 * @param array $eventInfo
464
+	 * @param array $oldEventInfo
465
+	 *
466
+	 * @return array
467
+	 */
468
+	protected function parseEventForOrganizer(VCalendar $calendar, array $eventInfo, array $oldEventInfo) {
469
+
470
+		// Merging attendee lists.
471
+		$attendees = [];
472
+		foreach ($oldEventInfo['attendees'] as $attendee) {
473
+			$attendees[$attendee['href']] = [
474
+				'href'         => $attendee['href'],
475
+				'oldInstances' => $attendee['instances'],
476
+				'newInstances' => [],
477
+				'name'         => $attendee['name'],
478
+				'forceSend'    => null,
479
+			];
480
+		}
481
+		foreach ($eventInfo['attendees'] as $attendee) {
482
+			if (isset($attendees[$attendee['href']])) {
483
+				$attendees[$attendee['href']]['name'] = $attendee['name'];
484
+				$attendees[$attendee['href']]['newInstances'] = $attendee['instances'];
485
+				$attendees[$attendee['href']]['forceSend'] = $attendee['forceSend'];
486
+			} else {
487
+				$attendees[$attendee['href']] = [
488
+					'href'         => $attendee['href'],
489
+					'oldInstances' => [],
490
+					'newInstances' => $attendee['instances'],
491
+					'name'         => $attendee['name'],
492
+					'forceSend'    => $attendee['forceSend'],
493
+				];
494
+			}
495
+		}
496
+
497
+		$messages = [];
498
+
499
+		foreach ($attendees as $attendee) {
500
+
501
+			// An organizer can also be an attendee. We should not generate any
502
+			// messages for those.
503
+			if ($attendee['href'] === $eventInfo['organizer']) {
504
+				continue;
505
+			}
506
+
507
+			$message = new Message();
508
+			$message->uid = $eventInfo['uid'];
509
+			$message->component = 'VEVENT';
510
+			$message->sequence = $eventInfo['sequence'];
511
+			$message->sender = $eventInfo['organizer'];
512
+			$message->senderName = $eventInfo['organizerName'];
513
+			$message->recipient = $attendee['href'];
514
+			$message->recipientName = $attendee['name'];
515
+
516
+			if (!$attendee['newInstances']) {
517
+
518
+				// If there are no instances the attendee is a part of, it
519
+				// means the attendee was removed and we need to send him a
520
+				// CANCEL.
521
+				$message->method = 'CANCEL';
522
+
523
+				// Creating the new iCalendar body.
524
+				$icalMsg = new VCalendar();
525
+				$icalMsg->METHOD = $message->method;
526
+				$event = $icalMsg->add('VEVENT', [
527
+					'UID'      => $message->uid,
528
+					'SEQUENCE' => $message->sequence,
529
+				]);
530
+				if (isset($calendar->VEVENT->SUMMARY)) {
531
+					$event->add('SUMMARY', $calendar->VEVENT->SUMMARY->getValue());
532
+				}
533
+				$event->add(clone $calendar->VEVENT->DTSTART);
534
+				if (isset($calendar->VEVENT->DTEND)) {
535
+					$event->add(clone $calendar->VEVENT->DTEND);
536
+				} elseif (isset($calendar->VEVENT->DURATION)) {
537
+					$event->add(clone $calendar->VEVENT->DURATION);
538
+				}
539
+				$org = $event->add('ORGANIZER', $eventInfo['organizer']);
540
+				if ($eventInfo['organizerName']) $org['CN'] = $eventInfo['organizerName'];
541
+				$event->add('ATTENDEE', $attendee['href'], [
542
+					'CN' => $attendee['name'],
543
+				]);
544
+				$message->significantChange = true;
545
+
546
+			} else {
547
+
548
+				// The attendee gets the updated event body
549
+				$message->method = 'REQUEST';
550
+
551
+				// Creating the new iCalendar body.
552
+				$icalMsg = new VCalendar();
553
+				$icalMsg->METHOD = $message->method;
554
+
555
+				foreach ($calendar->select('VTIMEZONE') as $timezone) {
556
+					$icalMsg->add(clone $timezone);
557
+				}
558
+
559
+				// We need to find out that this change is significant. If it's
560
+				// not, systems may opt to not send messages.
561
+				//
562
+				// We do this based on the 'significantChangeHash' which is
563
+				// some value that changes if there's a certain set of
564
+				// properties changed in the event, or simply if there's a
565
+				// difference in instances that the attendee is invited to.
566
+
567
+				$message->significantChange =
568
+					$attendee['forceSend'] === 'REQUEST' ||
569
+					array_keys($attendee['oldInstances']) != array_keys($attendee['newInstances']) ||
570
+					$oldEventInfo['significantChangeHash'] !== $eventInfo['significantChangeHash'];
571
+
572
+				foreach ($attendee['newInstances'] as $instanceId => $instanceInfo) {
573
+
574
+					$currentEvent = clone $eventInfo['instances'][$instanceId];
575
+					if ($instanceId === 'master') {
576
+
577
+						// We need to find a list of events that the attendee
578
+						// is not a part of to add to the list of exceptions.
579
+						$exceptions = [];
580
+						foreach ($eventInfo['instances'] as $instanceId => $vevent) {
581
+							if (!isset($attendee['newInstances'][$instanceId])) {
582
+								$exceptions[] = $instanceId;
583
+							}
584
+						}
585
+
586
+						// If there were exceptions, we need to add it to an
587
+						// existing EXDATE property, if it exists.
588
+						if ($exceptions) {
589
+							if (isset($currentEvent->EXDATE)) {
590
+								$currentEvent->EXDATE->setParts(array_merge(
591
+									$currentEvent->EXDATE->getParts(),
592
+									$exceptions
593
+								));
594
+							} else {
595
+								$currentEvent->EXDATE = $exceptions;
596
+							}
597
+						}
598
+
599
+						// Cleaning up any scheduling information that
600
+						// shouldn't be sent along.
601
+						unset($currentEvent->ORGANIZER['SCHEDULE-FORCE-SEND']);
602
+						unset($currentEvent->ORGANIZER['SCHEDULE-STATUS']);
603
+
604
+						foreach ($currentEvent->ATTENDEE as $attendee) {
605
+							unset($attendee['SCHEDULE-FORCE-SEND']);
606
+							unset($attendee['SCHEDULE-STATUS']);
607
+
608
+							// We're adding PARTSTAT=NEEDS-ACTION to ensure that
609
+							// iOS shows an "Inbox Item"
610
+							if (!isset($attendee['PARTSTAT'])) {
611
+								$attendee['PARTSTAT'] = 'NEEDS-ACTION';
612
+							}
613
+
614
+						}
615
+
616
+					}
617
+
618
+					$icalMsg->add($currentEvent);
619
+
620
+				}
621
+
622
+			}
623
+
624
+			$message->message = $icalMsg;
625
+			$messages[] = $message;
626
+
627
+		}
628
+
629
+		return $messages;
630
+
631
+	}
632
+
633
+	/**
634
+	 * Parse an event update for an attendee.
635
+	 *
636
+	 * This function figures out if we need to send a reply to an organizer.
637
+	 *
638
+	 * @param VCalendar $calendar
639
+	 * @param array $eventInfo
640
+	 * @param array $oldEventInfo
641
+	 * @param string $attendee
642
+	 *
643
+	 * @return Message[]
644
+	 */
645
+	protected function parseEventForAttendee(VCalendar $calendar, array $eventInfo, array $oldEventInfo, $attendee) {
646
+
647
+		if ($this->scheduleAgentServerRules && $eventInfo['organizerScheduleAgent'] === 'CLIENT') {
648
+			return [];
649
+		}
650
+
651
+		// Don't bother generating messages for events that have already been
652
+		// cancelled.
653
+		if ($eventInfo['status'] === 'CANCELLED') {
654
+			return [];
655
+		}
656
+
657
+		$oldInstances = !empty($oldEventInfo['attendees'][$attendee]['instances']) ?
658
+			$oldEventInfo['attendees'][$attendee]['instances'] :
659
+			[];
660
+
661
+		$instances = [];
662
+		foreach ($oldInstances as $instance) {
663
+
664
+			$instances[$instance['id']] = [
665
+				'id'        => $instance['id'],
666
+				'oldstatus' => $instance['partstat'],
667
+				'newstatus' => null,
668
+			];
669
+
670
+		}
671
+		foreach ($eventInfo['attendees'][$attendee]['instances'] as $instance) {
672
+
673
+			if (isset($instances[$instance['id']])) {
674
+				$instances[$instance['id']]['newstatus'] = $instance['partstat'];
675
+			} else {
676
+				$instances[$instance['id']] = [
677
+					'id'        => $instance['id'],
678
+					'oldstatus' => null,
679
+					'newstatus' => $instance['partstat'],
680
+				];
681
+			}
682
+
683
+		}
684
+
685
+		// We need to also look for differences in EXDATE. If there are new
686
+		// items in EXDATE, it means that an attendee deleted instances of an
687
+		// event, which means we need to send DECLINED specifically for those
688
+		// instances.
689
+		// We only need to do that though, if the master event is not declined.
690
+		if (isset($instances['master']) && $instances['master']['newstatus'] !== 'DECLINED') {
691
+			foreach ($eventInfo['exdate'] as $exDate) {
692
+
693
+				if (!in_array($exDate, $oldEventInfo['exdate'])) {
694
+					if (isset($instances[$exDate])) {
695
+						$instances[$exDate]['newstatus'] = 'DECLINED';
696
+					} else {
697
+						$instances[$exDate] = [
698
+							'id'        => $exDate,
699
+							'oldstatus' => null,
700
+							'newstatus' => 'DECLINED',
701
+						];
702
+					}
703
+				}
704
+
705
+			}
706
+		}
707
+
708
+		// Gathering a few extra properties for each instance.
709
+		foreach ($instances as $recurId => $instanceInfo) {
710
+
711
+			if (isset($eventInfo['instances'][$recurId])) {
712
+				$instances[$recurId]['dtstart'] = clone $eventInfo['instances'][$recurId]->DTSTART;
713
+			} else {
714
+				$instances[$recurId]['dtstart'] = $recurId;
715
+			}
716
+
717
+		}
718
+
719
+		$message = new Message();
720
+		$message->uid = $eventInfo['uid'];
721
+		$message->method = 'REPLY';
722
+		$message->component = 'VEVENT';
723
+		$message->sequence = $eventInfo['sequence'];
724
+		$message->sender = $attendee;
725
+		$message->senderName = $eventInfo['attendees'][$attendee]['name'];
726
+		$message->recipient = $eventInfo['organizer'];
727
+		$message->recipientName = $eventInfo['organizerName'];
728
+
729
+		$icalMsg = new VCalendar();
730
+		$icalMsg->METHOD = 'REPLY';
731
+
732
+		$hasReply = false;
733
+
734
+		foreach ($instances as $instance) {
735
+
736
+			if ($instance['oldstatus'] == $instance['newstatus'] && $eventInfo['organizerForceSend'] !== 'REPLY') {
737
+				// Skip
738
+				continue;
739
+			}
740
+
741
+			$event = $icalMsg->add('VEVENT', [
742
+				'UID'      => $message->uid,
743
+				'SEQUENCE' => $message->sequence,
744
+			]);
745
+			$summary = isset($calendar->VEVENT->SUMMARY) ? $calendar->VEVENT->SUMMARY->getValue() : '';
746
+			// Adding properties from the correct source instance
747
+			if (isset($eventInfo['instances'][$instance['id']])) {
748
+				$instanceObj = $eventInfo['instances'][$instance['id']];
749
+				$event->add(clone $instanceObj->DTSTART);
750
+				if (isset($instanceObj->DTEND)) {
751
+					$event->add(clone $instanceObj->DTEND);
752
+				} elseif (isset($instanceObj->DURATION)) {
753
+					$event->add(clone $instanceObj->DURATION);
754
+				}
755
+				if (isset($instanceObj->SUMMARY)) {
756
+					$event->add('SUMMARY', $instanceObj->SUMMARY->getValue());
757
+				} elseif ($summary) {
758
+					$event->add('SUMMARY', $summary);
759
+				}
760
+			} else {
761
+				// This branch of the code is reached, when a reply is
762
+				// generated for an instance of a recurring event, through the
763
+				// fact that the instance has disappeared by showing up in
764
+				// EXDATE
765
+				$dt = DateTimeParser::parse($instance['id'], $eventInfo['timezone']);
766
+				// Treat is as a DATE field
767
+				if (strlen($instance['id']) <= 8) {
768
+					$event->add('DTSTART', $dt, ['VALUE' => 'DATE']);
769
+				} else {
770
+					$event->add('DTSTART', $dt);
771
+				}
772
+				if ($summary) {
773
+					$event->add('SUMMARY', $summary);
774
+				}
775
+			}
776
+			if ($instance['id'] !== 'master') {
777
+				$dt = DateTimeParser::parse($instance['id'], $eventInfo['timezone']);
778
+				// Treat is as a DATE field
779
+				if (strlen($instance['id']) <= 8) {
780
+					$event->add('RECURRENCE-ID', $dt, ['VALUE' => 'DATE']);
781
+				} else {
782
+					$event->add('RECURRENCE-ID', $dt);
783
+				}
784
+			}
785
+			$organizer = $event->add('ORGANIZER', $message->recipient);
786
+			if ($message->recipientName) {
787
+				$organizer['CN'] = $message->recipientName;
788
+			}
789
+			$attendee = $event->add('ATTENDEE', $message->sender, [
790
+				'PARTSTAT' => $instance['newstatus']
791
+			]);
792
+			if ($message->senderName) {
793
+				$attendee['CN'] = $message->senderName;
794
+			}
795
+			$hasReply = true;
796
+
797
+		}
798
+
799
+		if ($hasReply) {
800
+			$message->message = $icalMsg;
801
+			return [$message];
802
+		} else {
803
+			return [];
804
+		}
805
+
806
+	}
807
+
808
+	/**
809
+	 * Returns attendee information and information about instances of an
810
+	 * event.
811
+	 *
812
+	 * Returns an array with the following keys:
813
+	 *
814
+	 * 1. uid
815
+	 * 2. organizer
816
+	 * 3. organizerName
817
+	 * 4. organizerScheduleAgent
818
+	 * 5. organizerForceSend
819
+	 * 6. instances
820
+	 * 7. attendees
821
+	 * 8. sequence
822
+	 * 9. exdate
823
+	 * 10. timezone - strictly the timezone on which the recurrence rule is
824
+	 *                based on.
825
+	 * 11. significantChangeHash
826
+	 * 12. status
827
+	 * @param VCalendar $calendar
828
+	 *
829
+	 * @return array
830
+	 */
831
+	protected function parseEventInfo(VCalendar $calendar = null) {
832
+
833
+		$uid = null;
834
+		$organizer = null;
835
+		$organizerName = null;
836
+		$organizerForceSend = null;
837
+		$sequence = null;
838
+		$timezone = null;
839
+		$status = null;
840
+		$organizerScheduleAgent = 'SERVER';
841
+
842
+		$significantChangeHash = '';
843
+
844
+		// Now we need to collect a list of attendees, and which instances they
845
+		// are a part of.
846
+		$attendees = [];
847
+
848
+		$instances = [];
849
+		$exdate = [];
850
+
851
+		foreach ($calendar->VEVENT as $vevent) {
852
+
853
+			if (is_null($uid)) {
854
+				$uid = $vevent->UID->getValue();
855
+			} else {
856
+				if ($uid !== $vevent->UID->getValue()) {
857
+					throw new ITipException('If a calendar contained more than one event, they must have the same UID.');
858
+				}
859
+			}
860
+
861
+			if (!isset($vevent->DTSTART)) {
862
+				throw new ITipException('An event MUST have a DTSTART property.');
863
+			}
864
+
865
+			if (isset($vevent->ORGANIZER)) {
866
+				if (is_null($organizer)) {
867
+					$organizer = $vevent->ORGANIZER->getNormalizedValue();
868
+					$organizerName = isset($vevent->ORGANIZER['CN']) ? $vevent->ORGANIZER['CN'] : null;
869
+				} else {
870
+					if ($organizer !== $vevent->ORGANIZER->getNormalizedValue()) {
871
+						throw new SameOrganizerForAllComponentsException('Every instance of the event must have the same organizer.');
872
+					}
873
+				}
874
+				$organizerForceSend =
875
+					isset($vevent->ORGANIZER['SCHEDULE-FORCE-SEND']) ?
876
+					strtoupper($vevent->ORGANIZER['SCHEDULE-FORCE-SEND']) :
877
+					null;
878
+				$organizerScheduleAgent =
879
+					isset($vevent->ORGANIZER['SCHEDULE-AGENT']) ?
880
+					strtoupper((string)$vevent->ORGANIZER['SCHEDULE-AGENT']) :
881
+					'SERVER';
882
+			}
883
+			if (is_null($sequence) && isset($vevent->SEQUENCE)) {
884
+				$sequence = $vevent->SEQUENCE->getValue();
885
+			}
886
+			if (isset($vevent->EXDATE)) {
887
+				foreach ($vevent->select('EXDATE') as $val) {
888
+					$exdate = array_merge($exdate, $val->getParts());
889
+				}
890
+				sort($exdate);
891
+			}
892
+			if (isset($vevent->STATUS)) {
893
+				$status = strtoupper($vevent->STATUS->getValue());
894
+			}
895
+
896
+			$recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master';
897
+			if (is_null($timezone)) {
898
+				if ($recurId === 'master') {
899
+					$timezone = $vevent->DTSTART->getDateTime()->getTimeZone();
900
+				} else {
901
+					$timezone = $vevent->{'RECURRENCE-ID'}->getDateTime()->getTimeZone();
902
+				}
903
+			}
904
+			if (isset($vevent->ATTENDEE)) {
905
+				foreach ($vevent->ATTENDEE as $attendee) {
906
+
907
+					if ($this->scheduleAgentServerRules &&
908
+						isset($attendee['SCHEDULE-AGENT']) &&
909
+						strtoupper($attendee['SCHEDULE-AGENT']->getValue()) === 'CLIENT'
910
+					) {
911
+						continue;
912
+					}
913
+					$partStat =
914
+						isset($attendee['PARTSTAT']) ?
915
+						strtoupper($attendee['PARTSTAT']) :
916
+						'NEEDS-ACTION';
917
+
918
+					$forceSend =
919
+						isset($attendee['SCHEDULE-FORCE-SEND']) ?
920
+						strtoupper($attendee['SCHEDULE-FORCE-SEND']) :
921
+						null;
922
+
923
+
924
+					if (isset($attendees[$attendee->getNormalizedValue()])) {
925
+						$attendees[$attendee->getNormalizedValue()]['instances'][$recurId] = [
926
+							'id'         => $recurId,
927
+							'partstat'   => $partStat,
928
+							'force-send' => $forceSend,
929
+						];
930
+					} else {
931
+						$attendees[$attendee->getNormalizedValue()] = [
932
+							'href'      => $attendee->getNormalizedValue(),
933
+							'instances' => [
934
+								$recurId => [
935
+									'id'       => $recurId,
936
+									'partstat' => $partStat,
937
+								],
938
+							],
939
+							'name'      => isset($attendee['CN']) ? (string)$attendee['CN'] : null,
940
+							'forceSend' => $forceSend,
941
+						];
942
+					}
943
+
944
+				}
945
+				$instances[$recurId] = $vevent;
946
+
947
+			}
948
+
949
+			foreach ($this->significantChangeProperties as $prop) {
950
+				if (isset($vevent->$prop)) {
951
+					$propertyValues = $vevent->select($prop);
952
+
953
+					$significantChangeHash .= $prop . ':';
954
+
955
+					if ($prop === 'EXDATE') {
956
+
957
+						$significantChangeHash .= implode(',', $exdate) . ';';
958
+
959
+					} else {
960
+
961
+						foreach ($propertyValues as $val) {
962
+							$significantChangeHash .= $val->getValue() . ';';
963
+						}
964
+
965
+					}
966
+				}
967
+			}
968
+
969
+		}
970
+		$significantChangeHash = md5($significantChangeHash);
971
+
972
+		return compact(
973
+			'uid',
974
+			'organizer',
975
+			'organizerName',
976
+			'organizerScheduleAgent',
977
+			'organizerForceSend',
978
+			'instances',
979
+			'attendees',
980
+			'sequence',
981
+			'exdate',
982
+			'timezone',
983
+			'significantChangeHash',
984
+			'status'
985
+		);
986
+
987
+	}
988 988
 
989 989
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/VObject/ITip/Message.php 1 patch
Indentation   +121 added lines, -121 removed lines patch added patch discarded remove patch
@@ -16,126 +16,126 @@
 block discarded – undo
16 16
  */
17 17
 class Message {
18 18
 
19
-    /**
20
-     * The object's UID.
21
-     *
22
-     * @var string
23
-     */
24
-    public $uid;
25
-
26
-    /**
27
-     * The component type, such as VEVENT.
28
-     *
29
-     * @var string
30
-     */
31
-    public $component;
32
-
33
-    /**
34
-     * Contains the ITip method, which is something like REQUEST, REPLY or
35
-     * CANCEL.
36
-     *
37
-     * @var string
38
-     */
39
-    public $method;
40
-
41
-    /**
42
-     * The current sequence number for the event.
43
-     *
44
-     * @var int
45
-     */
46
-    public $sequence;
47
-
48
-    /**
49
-     * The senders' email address.
50
-     *
51
-     * Note that this does not imply that this has to be used in a From: field
52
-     * if the message is sent by email. It may also be populated in Reply-To:
53
-     * or not at all.
54
-     *
55
-     * @var string
56
-     */
57
-    public $sender;
58
-
59
-    /**
60
-     * The name of the sender. This is often populated from a CN parameter from
61
-     * either the ORGANIZER or ATTENDEE, depending on the message.
62
-     *
63
-     * @var string|null
64
-     */
65
-    public $senderName;
66
-
67
-    /**
68
-     * The recipient's email address.
69
-     *
70
-     * @var string
71
-     */
72
-    public $recipient;
73
-
74
-    /**
75
-     * The name of the recipient. This is usually populated with the CN
76
-     * parameter from the ATTENDEE or ORGANIZER property, if it's available.
77
-     *
78
-     * @var string|null
79
-     */
80
-    public $recipientName;
81
-
82
-    /**
83
-     * After the message has been delivered, this should contain a string such
84
-     * as : 1.1;Sent or 1.2;Delivered.
85
-     *
86
-     * In case of a failure, this will hold the error status code.
87
-     *
88
-     * See:
89
-     * http://tools.ietf.org/html/rfc6638#section-7.3
90
-     *
91
-     * @var string
92
-     */
93
-    public $scheduleStatus;
94
-
95
-    /**
96
-     * The iCalendar / iTip body.
97
-     *
98
-     * @var \Sabre\VObject\Component\VCalendar
99
-     */
100
-    public $message;
101
-
102
-    /**
103
-     * This will be set to true, if the iTip broker considers the change
104
-     * 'significant'.
105
-     *
106
-     * In practice, this means that we'll only mark it true, if for instance
107
-     * DTSTART changed. This allows systems to only send iTip messages when
108
-     * significant changes happened. This is especially useful for iMip, as
109
-     * normally a ton of messages may be generated for normal calendar use.
110
-     *
111
-     * To see the list of properties that are considered 'significant', check
112
-     * out Sabre\VObject\ITip\Broker::$significantChangeProperties.
113
-     *
114
-     * @var bool
115
-     */
116
-    public $significantChange = true;
117
-
118
-    /**
119
-     * Returns the schedule status as a string.
120
-     *
121
-     * For example:
122
-     * 1.2
123
-     *
124
-     * @return mixed bool|string
125
-     */
126
-    public function getScheduleStatus() {
127
-
128
-        if (!$this->scheduleStatus) {
129
-
130
-            return false;
131
-
132
-        } else {
133
-
134
-            list($scheduleStatus) = explode(';', $this->scheduleStatus);
135
-            return $scheduleStatus;
136
-
137
-        }
138
-
139
-    }
19
+	/**
20
+	 * The object's UID.
21
+	 *
22
+	 * @var string
23
+	 */
24
+	public $uid;
25
+
26
+	/**
27
+	 * The component type, such as VEVENT.
28
+	 *
29
+	 * @var string
30
+	 */
31
+	public $component;
32
+
33
+	/**
34
+	 * Contains the ITip method, which is something like REQUEST, REPLY or
35
+	 * CANCEL.
36
+	 *
37
+	 * @var string
38
+	 */
39
+	public $method;
40
+
41
+	/**
42
+	 * The current sequence number for the event.
43
+	 *
44
+	 * @var int
45
+	 */
46
+	public $sequence;
47
+
48
+	/**
49
+	 * The senders' email address.
50
+	 *
51
+	 * Note that this does not imply that this has to be used in a From: field
52
+	 * if the message is sent by email. It may also be populated in Reply-To:
53
+	 * or not at all.
54
+	 *
55
+	 * @var string
56
+	 */
57
+	public $sender;
58
+
59
+	/**
60
+	 * The name of the sender. This is often populated from a CN parameter from
61
+	 * either the ORGANIZER or ATTENDEE, depending on the message.
62
+	 *
63
+	 * @var string|null
64
+	 */
65
+	public $senderName;
66
+
67
+	/**
68
+	 * The recipient's email address.
69
+	 *
70
+	 * @var string
71
+	 */
72
+	public $recipient;
73
+
74
+	/**
75
+	 * The name of the recipient. This is usually populated with the CN
76
+	 * parameter from the ATTENDEE or ORGANIZER property, if it's available.
77
+	 *
78
+	 * @var string|null
79
+	 */
80
+	public $recipientName;
81
+
82
+	/**
83
+	 * After the message has been delivered, this should contain a string such
84
+	 * as : 1.1;Sent or 1.2;Delivered.
85
+	 *
86
+	 * In case of a failure, this will hold the error status code.
87
+	 *
88
+	 * See:
89
+	 * http://tools.ietf.org/html/rfc6638#section-7.3
90
+	 *
91
+	 * @var string
92
+	 */
93
+	public $scheduleStatus;
94
+
95
+	/**
96
+	 * The iCalendar / iTip body.
97
+	 *
98
+	 * @var \Sabre\VObject\Component\VCalendar
99
+	 */
100
+	public $message;
101
+
102
+	/**
103
+	 * This will be set to true, if the iTip broker considers the change
104
+	 * 'significant'.
105
+	 *
106
+	 * In practice, this means that we'll only mark it true, if for instance
107
+	 * DTSTART changed. This allows systems to only send iTip messages when
108
+	 * significant changes happened. This is especially useful for iMip, as
109
+	 * normally a ton of messages may be generated for normal calendar use.
110
+	 *
111
+	 * To see the list of properties that are considered 'significant', check
112
+	 * out Sabre\VObject\ITip\Broker::$significantChangeProperties.
113
+	 *
114
+	 * @var bool
115
+	 */
116
+	public $significantChange = true;
117
+
118
+	/**
119
+	 * Returns the schedule status as a string.
120
+	 *
121
+	 * For example:
122
+	 * 1.2
123
+	 *
124
+	 * @return mixed bool|string
125
+	 */
126
+	public function getScheduleStatus() {
127
+
128
+		if (!$this->scheduleStatus) {
129
+
130
+			return false;
131
+
132
+		} else {
133
+
134
+			list($scheduleStatus) = explode(';', $this->scheduleStatus);
135
+			return $scheduleStatus;
136
+
137
+		}
138
+
139
+	}
140 140
 
141 141
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/VObject/Splitter/ICalendar.php 1 patch
Indentation   +88 added lines, -88 removed lines patch added patch discarded remove patch
@@ -21,93 +21,93 @@
 block discarded – undo
21 21
  */
22 22
 class ICalendar implements SplitterInterface {
23 23
 
24
-    /**
25
-     * Timezones.
26
-     *
27
-     * @var array
28
-     */
29
-    protected $vtimezones = [];
30
-
31
-    /**
32
-     * iCalendar objects.
33
-     *
34
-     * @var array
35
-     */
36
-    protected $objects = [];
37
-
38
-    /**
39
-     * Constructor.
40
-     *
41
-     * The splitter should receive an readable file stream as it's input.
42
-     *
43
-     * @param resource $input
44
-     * @param int $options Parser options, see the OPTIONS constants.
45
-     */
46
-    public function __construct($input, $options = 0) {
47
-
48
-        $data = VObject\Reader::read($input, $options);
49
-
50
-        if (!$data instanceof VObject\Component\VCalendar) {
51
-            throw new VObject\ParseException('Supplied input could not be parsed as VCALENDAR.');
52
-        }
53
-
54
-        foreach ($data->children() as $component) {
55
-            if (!$component instanceof VObject\Component) {
56
-                continue;
57
-            }
58
-
59
-            // Get all timezones
60
-            if ($component->name === 'VTIMEZONE') {
61
-                $this->vtimezones[(string)$component->TZID] = $component;
62
-                continue;
63
-            }
64
-
65
-            // Get component UID for recurring Events search
66
-            if (!$component->UID) {
67
-                $component->UID = sha1(microtime()) . '-vobjectimport';
68
-            }
69
-            $uid = (string)$component->UID;
70
-
71
-            // Take care of recurring events
72
-            if (!array_key_exists($uid, $this->objects)) {
73
-                $this->objects[$uid] = new VCalendar();
74
-            }
75
-
76
-            $this->objects[$uid]->add(clone $component);
77
-        }
78
-
79
-    }
80
-
81
-    /**
82
-     * Every time getNext() is called, a new object will be parsed, until we
83
-     * hit the end of the stream.
84
-     *
85
-     * When the end is reached, null will be returned.
86
-     *
87
-     * @return Sabre\VObject\Component|null
88
-     */
89
-    public function getNext() {
90
-
91
-        if ($object = array_shift($this->objects)) {
92
-
93
-            // create our baseobject
94
-            $object->version = '2.0';
95
-            $object->prodid = '-//Sabre//Sabre VObject ' . VObject\Version::VERSION . '//EN';
96
-            $object->calscale = 'GREGORIAN';
97
-
98
-            // add vtimezone information to obj (if we have it)
99
-            foreach ($this->vtimezones as $vtimezone) {
100
-                $object->add($vtimezone);
101
-            }
102
-
103
-            return $object;
104
-
105
-        } else {
106
-
107
-            return;
108
-
109
-        }
110
-
111
-    }
24
+	/**
25
+	 * Timezones.
26
+	 *
27
+	 * @var array
28
+	 */
29
+	protected $vtimezones = [];
30
+
31
+	/**
32
+	 * iCalendar objects.
33
+	 *
34
+	 * @var array
35
+	 */
36
+	protected $objects = [];
37
+
38
+	/**
39
+	 * Constructor.
40
+	 *
41
+	 * The splitter should receive an readable file stream as it's input.
42
+	 *
43
+	 * @param resource $input
44
+	 * @param int $options Parser options, see the OPTIONS constants.
45
+	 */
46
+	public function __construct($input, $options = 0) {
47
+
48
+		$data = VObject\Reader::read($input, $options);
49
+
50
+		if (!$data instanceof VObject\Component\VCalendar) {
51
+			throw new VObject\ParseException('Supplied input could not be parsed as VCALENDAR.');
52
+		}
53
+
54
+		foreach ($data->children() as $component) {
55
+			if (!$component instanceof VObject\Component) {
56
+				continue;
57
+			}
58
+
59
+			// Get all timezones
60
+			if ($component->name === 'VTIMEZONE') {
61
+				$this->vtimezones[(string)$component->TZID] = $component;
62
+				continue;
63
+			}
64
+
65
+			// Get component UID for recurring Events search
66
+			if (!$component->UID) {
67
+				$component->UID = sha1(microtime()) . '-vobjectimport';
68
+			}
69
+			$uid = (string)$component->UID;
70
+
71
+			// Take care of recurring events
72
+			if (!array_key_exists($uid, $this->objects)) {
73
+				$this->objects[$uid] = new VCalendar();
74
+			}
75
+
76
+			$this->objects[$uid]->add(clone $component);
77
+		}
78
+
79
+	}
80
+
81
+	/**
82
+	 * Every time getNext() is called, a new object will be parsed, until we
83
+	 * hit the end of the stream.
84
+	 *
85
+	 * When the end is reached, null will be returned.
86
+	 *
87
+	 * @return Sabre\VObject\Component|null
88
+	 */
89
+	public function getNext() {
90
+
91
+		if ($object = array_shift($this->objects)) {
92
+
93
+			// create our baseobject
94
+			$object->version = '2.0';
95
+			$object->prodid = '-//Sabre//Sabre VObject ' . VObject\Version::VERSION . '//EN';
96
+			$object->calscale = 'GREGORIAN';
97
+
98
+			// add vtimezone information to obj (if we have it)
99
+			foreach ($this->vtimezones as $vtimezone) {
100
+				$object->add($vtimezone);
101
+			}
102
+
103
+			return $object;
104
+
105
+		} else {
106
+
107
+			return;
108
+
109
+		}
110
+
111
+	}
112 112
 
113 113
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/VObject/Splitter/VCard.php 1 patch
Indentation   +43 added lines, -43 removed lines patch added patch discarded remove patch
@@ -21,58 +21,58 @@
 block discarded – undo
21 21
  */
22 22
 class VCard implements SplitterInterface {
23 23
 
24
-    /**
25
-     * File handle.
26
-     *
27
-     * @var resource
28
-     */
29
-    protected $input;
24
+	/**
25
+	 * File handle.
26
+	 *
27
+	 * @var resource
28
+	 */
29
+	protected $input;
30 30
 
31
-    /**
32
-     * Persistent parser.
33
-     *
34
-     * @var MimeDir
35
-     */
36
-    protected $parser;
31
+	/**
32
+	 * Persistent parser.
33
+	 *
34
+	 * @var MimeDir
35
+	 */
36
+	protected $parser;
37 37
 
38
-    /**
39
-     * Constructor.
40
-     *
41
-     * The splitter should receive an readable file stream as it's input.
42
-     *
43
-     * @param resource $input
44
-     * @param int $options Parser options, see the OPTIONS constants.
45
-     */
46
-    public function __construct($input, $options = 0) {
38
+	/**
39
+	 * Constructor.
40
+	 *
41
+	 * The splitter should receive an readable file stream as it's input.
42
+	 *
43
+	 * @param resource $input
44
+	 * @param int $options Parser options, see the OPTIONS constants.
45
+	 */
46
+	public function __construct($input, $options = 0) {
47 47
 
48
-        $this->input = $input;
49
-        $this->parser = new MimeDir($input, $options);
48
+		$this->input = $input;
49
+		$this->parser = new MimeDir($input, $options);
50 50
 
51
-    }
51
+	}
52 52
 
53
-    /**
54
-     * Every time getNext() is called, a new object will be parsed, until we
55
-     * hit the end of the stream.
56
-     *
57
-     * When the end is reached, null will be returned.
58
-     *
59
-     * @return Sabre\VObject\Component|null
60
-     */
61
-    public function getNext() {
53
+	/**
54
+	 * Every time getNext() is called, a new object will be parsed, until we
55
+	 * hit the end of the stream.
56
+	 *
57
+	 * When the end is reached, null will be returned.
58
+	 *
59
+	 * @return Sabre\VObject\Component|null
60
+	 */
61
+	public function getNext() {
62 62
 
63
-        try {
64
-            $object = $this->parser->parse();
63
+		try {
64
+			$object = $this->parser->parse();
65 65
 
66
-            if (!$object instanceof VObject\Component\VCard) {
67
-                throw new VObject\ParseException('The supplied input contained non-VCARD data.');
68
-            }
66
+			if (!$object instanceof VObject\Component\VCard) {
67
+				throw new VObject\ParseException('The supplied input contained non-VCARD data.');
68
+			}
69 69
 
70
-        } catch (VObject\EofException $e) {
71
-            return;
72
-        }
70
+		} catch (VObject\EofException $e) {
71
+			return;
72
+		}
73 73
 
74
-        return $object;
74
+		return $object;
75 75
 
76
-    }
76
+	}
77 77
 
78 78
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/VObject/Splitter/SplitterInterface.php 1 patch
Indentation   +17 added lines, -17 removed lines patch added patch discarded remove patch
@@ -17,23 +17,23 @@
 block discarded – undo
17 17
  */
18 18
 interface SplitterInterface {
19 19
 
20
-    /**
21
-     * Constructor.
22
-     *
23
-     * The splitter should receive an readable file stream as it's input.
24
-     *
25
-     * @param resource $input
26
-     */
27
-    public function __construct($input);
20
+	/**
21
+	 * Constructor.
22
+	 *
23
+	 * The splitter should receive an readable file stream as it's input.
24
+	 *
25
+	 * @param resource $input
26
+	 */
27
+	public function __construct($input);
28 28
 
29
-    /**
30
-     * Every time getNext() is called, a new object will be parsed, until we
31
-     * hit the end of the stream.
32
-     *
33
-     * When the end is reached, null will be returned.
34
-     *
35
-     * @return Sabre\VObject\Component|null
36
-     */
37
-    public function getNext();
29
+	/**
30
+	 * Every time getNext() is called, a new object will be parsed, until we
31
+	 * hit the end of the stream.
32
+	 *
33
+	 * When the end is reached, null will be returned.
34
+	 *
35
+	 * @return Sabre\VObject\Component|null
36
+	 */
37
+	public function getNext();
38 38
 
39 39
 }
Please login to merge, or discard this patch.