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