]> git.mxchange.org Git - friendica-addons.git/blob - dav/sabre-vobject/lib/Sabre/VObject/Component.php
Merge remote branch 'friendica/master'
[friendica-addons.git] / dav / sabre-vobject / lib / Sabre / VObject / Component.php
1 <?php
2
3 namespace Sabre\VObject;
4
5 /**
6  * VObject Component
7  *
8  * This class represents a VCALENDAR/VCARD component. A component is for example
9  * VEVENT, VTODO and also VCALENDAR. It starts with BEGIN:COMPONENTNAME and
10  * ends with END:COMPONENTNAME
11  *
12  * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
13  * @author Evert Pot (http://www.rooftopsolutions.nl/)
14  * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
15  */
16 class Component extends Element {
17
18     /**
19      * Name, for example VEVENT
20      *
21      * @var string
22      */
23     public $name;
24
25     /**
26      * Children properties and components
27      *
28      * @var array
29      */
30     public $children = array();
31
32     /**
33      * The following constants are used by the validate() method.
34      */
35     const REPAIR = 1;
36
37     /**
38      * If components are added to this map, they will be automatically mapped
39      * to their respective classes, if parsed by the reader or constructed with
40      * the 'create' method.
41      *
42      * @var array
43      */
44     static public $classMap = array(
45         'VALARM'        => 'Sabre\\VObject\\Component\\VAlarm',
46         'VCALENDAR'     => 'Sabre\\VObject\\Component\\VCalendar',
47         'VCARD'         => 'Sabre\\VObject\\Component\\VCard',
48         'VEVENT'        => 'Sabre\\VObject\\Component\\VEvent',
49         'VJOURNAL'      => 'Sabre\\VObject\\Component\\VJournal',
50         'VTODO'         => 'Sabre\\VObject\\Component\\VTodo',
51     );
52
53     /**
54      * Creates the new component by name, but in addition will also see if
55      * there's a class mapped to the property name.
56      *
57      * @param string $name
58      * @param string $value
59      * @return Component
60      */
61     static public function create($name, $value = null) {
62
63         $name = strtoupper($name);
64
65         if (isset(self::$classMap[$name])) {
66             return new self::$classMap[$name]($name, $value);
67         } else {
68             return new self($name, $value);
69         }
70
71     }
72
73     /**
74      * Creates a new component.
75      *
76      * By default this object will iterate over its own children, but this can
77      * be overridden with the iterator argument
78      *
79      * @param string $name
80      * @param ElementList $iterator
81      */
82     public function __construct($name, ElementList $iterator = null) {
83
84         $this->name = strtoupper($name);
85         if (!is_null($iterator)) $this->iterator = $iterator;
86
87     }
88
89     /**
90      * Turns the object back into a serialized blob.
91      *
92      * @return string
93      */
94     public function serialize() {
95
96         $str = "BEGIN:" . $this->name . "\r\n";
97
98         /**
99          * Gives a component a 'score' for sorting purposes.
100          *
101          * This is solely used by the childrenSort method.
102          *
103          * A higher score means the item will be lower in the list.
104          * To avoid score collisions, each "score category" has a reasonable
105          * space to accomodate elements. The $key is added to the $score to
106          * preserve the original relative order of elements.
107          *
108          * @param int $key
109          * @param array $array
110          * @return int
111          */
112         $sortScore = function($key, $array) {
113
114             if ($array[$key] instanceof Component) {
115
116                 // We want to encode VTIMEZONE first, this is a personal
117                 // preference.
118                 if ($array[$key]->name === 'VTIMEZONE') {
119                     $score=300000000;
120                     return $score+$key;
121                 } else {
122                     $score=400000000;
123                     return $score+$key;
124                 }
125             } else {
126                 // Properties get encoded first
127                 // VCARD version 4.0 wants the VERSION property to appear first
128                 if ($array[$key] instanceof Property) {
129                     if ($array[$key]->name === 'VERSION') {
130                         $score=100000000;
131                         return $score+$key;
132                     } else {
133                         // All other properties
134                         $score=200000000;
135                         return $score+$key;
136                     }
137                 }
138             }
139
140         };
141
142         $tmp = $this->children;
143         uksort($this->children, function($a, $b) use ($sortScore, $tmp) {
144
145             $sA = $sortScore($a, $tmp);
146             $sB = $sortScore($b, $tmp);
147
148             if ($sA === $sB) return 0;
149
150             return ($sA < $sB) ? -1 : 1;
151
152         });
153
154         foreach($this->children as $child) $str.=$child->serialize();
155         $str.= "END:" . $this->name . "\r\n";
156
157         return $str;
158
159     }
160
161     /**
162      * Adds a new component or element
163      *
164      * You can call this method with the following syntaxes:
165      *
166      * add(Element $element)
167      * add(string $name, $value, array $parameters = array())
168      *
169      * The first version adds an Element
170      * The second adds a property as a string.
171      *
172      * @param mixed $item
173      * @param mixed $itemValue
174      * @return void
175      */
176     public function add($item, $itemValue = null, array $parameters = array()) {
177
178         if ($item instanceof Element) {
179             if (!is_null($itemValue)) {
180                 throw new \InvalidArgumentException('The second argument must not be specified, when passing a VObject');
181             }
182             $item->parent = $this;
183             $this->children[] = $item;
184         } elseif(is_string($item)) {
185
186             if (!is_scalar($itemValue)) {
187                 throw new \InvalidArgumentException('The second argument must be scalar');
188             }
189             $item = Property::create($item,$itemValue, $parameters);
190             $item->parent = $this;
191             $this->children[] = $item;
192
193         } else {
194
195             throw new \InvalidArgumentException('The first argument must either be a \\Sabre\\VObject\\Element or a string');
196
197         }
198
199     }
200
201     /**
202      * Returns an iterable list of children
203      *
204      * @return ElementList
205      */
206     public function children() {
207
208         return new ElementList($this->children);
209
210     }
211
212     /**
213      * Returns an array with elements that match the specified name.
214      *
215      * This function is also aware of MIME-Directory groups (as they appear in
216      * vcards). This means that if a property is grouped as "HOME.EMAIL", it
217      * will also be returned when searching for just "EMAIL". If you want to
218      * search for a property in a specific group, you can select on the entire
219      * string ("HOME.EMAIL"). If you want to search on a specific property that
220      * has not been assigned a group, specify ".EMAIL".
221      *
222      * Keys are retained from the 'children' array, which may be confusing in
223      * certain cases.
224      *
225      * @param string $name
226      * @return array
227      */
228     public function select($name) {
229
230         $group = null;
231         $name = strtoupper($name);
232         if (strpos($name,'.')!==false) {
233             list($group,$name) = explode('.', $name, 2);
234         }
235
236         $result = array();
237         foreach($this->children as $key=>$child) {
238
239             if (
240                 strtoupper($child->name) === $name &&
241                 (is_null($group) || ( $child instanceof Property && strtoupper($child->group) === $group))
242             ) {
243
244                 $result[$key] = $child;
245
246             }
247         }
248
249         reset($result);
250         return $result;
251
252     }
253
254     /**
255      * This method only returns a list of sub-components. Properties are
256      * ignored.
257      *
258      * @return array
259      */
260     public function getComponents() {
261
262         $result = array();
263         foreach($this->children as $child) {
264             if ($child instanceof Component) {
265                 $result[] = $child;
266             }
267         }
268
269         return $result;
270
271     }
272
273     /**
274      * Validates the node for correctness.
275      *
276      * The following options are supported:
277      *   - Component::REPAIR - If something is broken, and automatic repair may
278      *                         be attempted.
279      *
280      * An array is returned with warnings.
281      *
282      * Every item in the array has the following properties:
283      *    * level - (number between 1 and 3 with severity information)
284      *    * message - (human readable message)
285      *    * node - (reference to the offending node)
286      *
287      * @param int $options
288      * @return array
289      */
290     public function validate($options = 0) {
291
292         $result = array();
293         foreach($this->children as $child) {
294             $result = array_merge($result, $child->validate());
295         }
296         return $result;
297
298     }
299
300     /* Magic property accessors {{{ */
301
302     /**
303      * Using 'get' you will either get a property or component,
304      *
305      * If there were no child-elements found with the specified name,
306      * null is returned.
307      *
308      * @param string $name
309      * @return Property
310      */
311     public function __get($name) {
312
313         $matches = $this->select($name);
314         if (count($matches)===0) {
315             return null;
316         } else {
317             $firstMatch = current($matches);
318             /** @var $firstMatch Property */
319             $firstMatch->setIterator(new ElementList(array_values($matches)));
320             return $firstMatch;
321         }
322
323     }
324
325     /**
326      * This method checks if a sub-element with the specified name exists.
327      *
328      * @param string $name
329      * @return bool
330      */
331     public function __isset($name) {
332
333         $matches = $this->select($name);
334         return count($matches)>0;
335
336     }
337
338     /**
339      * Using the setter method you can add properties or subcomponents
340      *
341      * You can either pass a Component, Property
342      * object, or a string to automatically create a Property.
343      *
344      * If the item already exists, it will be removed. If you want to add
345      * a new item with the same name, always use the add() method.
346      *
347      * @param string $name
348      * @param mixed $value
349      * @return void
350      */
351     public function __set($name, $value) {
352
353         $matches = $this->select($name);
354         $overWrite = count($matches)?key($matches):null;
355
356         if ($value instanceof Component || $value instanceof Property) {
357             $value->parent = $this;
358             if (!is_null($overWrite)) {
359                 $this->children[$overWrite] = $value;
360             } else {
361                 $this->children[] = $value;
362             }
363         } elseif (is_scalar($value)) {
364             $property = Property::create($name,$value);
365             $property->parent = $this;
366             if (!is_null($overWrite)) {
367                 $this->children[$overWrite] = $property;
368             } else {
369                 $this->children[] = $property;
370             }
371         } else {
372             throw new \InvalidArgumentException('You must pass a \\Sabre\\VObject\\Component, \\Sabre\\VObject\\Property or scalar type');
373         }
374
375     }
376
377     /**
378      * Removes all properties and components within this component.
379      *
380      * @param string $name
381      * @return void
382      */
383     public function __unset($name) {
384
385         $matches = $this->select($name);
386         foreach($matches as $k=>$child) {
387
388             unset($this->children[$k]);
389             $child->parent = null;
390
391         }
392
393     }
394
395     /* }}} */
396
397     /**
398      * This method is automatically called when the object is cloned.
399      * Specifically, this will ensure all child elements are also cloned.
400      *
401      * @return void
402      */
403     public function __clone() {
404
405         foreach($this->children as $key=>$child) {
406             $this->children[$key] = clone $child;
407             $this->children[$key]->parent = $this;
408         }
409
410     }
411
412 }