Mercurial > hg > rc1
comparison vendor/sabre/vobject/lib/Component.php @ 7:430dbd5346f7
vendor sabre as distributed
author | Charlie Root |
---|---|
date | Sat, 13 Jan 2018 09:06:10 -0500 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
6:cec75ba50afc | 7:430dbd5346f7 |
---|---|
1 <?php | |
2 | |
3 namespace Sabre\VObject; | |
4 | |
5 /** | |
6 * Component | |
7 * | |
8 * A component represents a group of properties, such as VCALENDAR, VEVENT, or | |
9 * VCARD. | |
10 * | |
11 * @copyright Copyright (C) 2011-2015 fruux GmbH (https://fruux.com/). | |
12 * @author Evert Pot (http://evertpot.com/) | |
13 * @license http://sabre.io/license/ Modified BSD License | |
14 */ | |
15 class Component extends Node { | |
16 | |
17 /** | |
18 * Component name. | |
19 * | |
20 * This will contain a string such as VEVENT, VTODO, VCALENDAR, VCARD. | |
21 * | |
22 * @var string | |
23 */ | |
24 public $name; | |
25 | |
26 /** | |
27 * A list of properties and/or sub-components. | |
28 * | |
29 * @var array | |
30 */ | |
31 public $children = array(); | |
32 | |
33 /** | |
34 * Creates a new component. | |
35 * | |
36 * You can specify the children either in key=>value syntax, in which case | |
37 * properties will automatically be created, or you can just pass a list of | |
38 * Component and Property object. | |
39 * | |
40 * By default, a set of sensible values will be added to the component. For | |
41 * an iCalendar object, this may be something like CALSCALE:GREGORIAN. To | |
42 * ensure that this does not happen, set $defaults to false. | |
43 * | |
44 * @param Document $root | |
45 * @param string $name such as VCALENDAR, VEVENT. | |
46 * @param array $children | |
47 * @param bool $defaults | |
48 * @return void | |
49 */ | |
50 function __construct(Document $root, $name, array $children = array(), $defaults = true) { | |
51 | |
52 $this->name = strtoupper($name); | |
53 $this->root = $root; | |
54 | |
55 if ($defaults) { | |
56 // This is a terribly convoluted way to do this, but this ensures | |
57 // that the order of properties as they are specified in both | |
58 // defaults and the childrens list, are inserted in the object in a | |
59 // natural way. | |
60 $list = $this->getDefaults(); | |
61 $nodes = array(); | |
62 foreach($children as $key=>$value) { | |
63 if ($value instanceof Node) { | |
64 if (isset($list[$value->name])) { | |
65 unset($list[$value->name]); | |
66 } | |
67 $nodes[] = $value; | |
68 } else { | |
69 $list[$key] = $value; | |
70 } | |
71 } | |
72 foreach($list as $key=>$value) { | |
73 $this->add($key, $value); | |
74 } | |
75 foreach($nodes as $node) { | |
76 $this->add($node); | |
77 } | |
78 } else { | |
79 foreach($children as $k=>$child) { | |
80 if ($child instanceof Node) { | |
81 | |
82 // Component or Property | |
83 $this->add($child); | |
84 } else { | |
85 | |
86 // Property key=>value | |
87 $this->add($k, $child); | |
88 } | |
89 } | |
90 } | |
91 | |
92 } | |
93 | |
94 /** | |
95 * Adds a new property or component, and returns the new item. | |
96 * | |
97 * This method has 3 possible signatures: | |
98 * | |
99 * add(Component $comp) // Adds a new component | |
100 * add(Property $prop) // Adds a new property | |
101 * add($name, $value, array $parameters = array()) // Adds a new property | |
102 * add($name, array $children = array()) // Adds a new component | |
103 * by name. | |
104 * | |
105 * @return Node | |
106 */ | |
107 function add($a1, $a2 = null, $a3 = null) { | |
108 | |
109 if ($a1 instanceof Node) { | |
110 if (!is_null($a2)) { | |
111 throw new \InvalidArgumentException('The second argument must not be specified, when passing a VObject Node'); | |
112 } | |
113 $a1->parent = $this; | |
114 $this->children[] = $a1; | |
115 | |
116 return $a1; | |
117 | |
118 } elseif(is_string($a1)) { | |
119 | |
120 $item = $this->root->create($a1, $a2, $a3); | |
121 $item->parent = $this; | |
122 $this->children[] = $item; | |
123 | |
124 return $item; | |
125 | |
126 } else { | |
127 | |
128 throw new \InvalidArgumentException('The first argument must either be a \\Sabre\\VObject\\Node or a string'); | |
129 | |
130 } | |
131 | |
132 } | |
133 | |
134 /** | |
135 * This method removes a component or property from this component. | |
136 * | |
137 * You can either specify the item by name (like DTSTART), in which case | |
138 * all properties/components with that name will be removed, or you can | |
139 * pass an instance of a property or component, in which case only that | |
140 * exact item will be removed. | |
141 * | |
142 * The removed item will be returned. In case there were more than 1 items | |
143 * removed, only the last one will be returned. | |
144 * | |
145 * @param mixed $item | |
146 * @return void | |
147 */ | |
148 function remove($item) { | |
149 | |
150 if (is_string($item)) { | |
151 $children = $this->select($item); | |
152 foreach($children as $k=>$child) { | |
153 unset($this->children[$k]); | |
154 } | |
155 return $child; | |
156 } else { | |
157 foreach($this->children as $k => $child) { | |
158 if ($child===$item) { | |
159 unset($this->children[$k]); | |
160 return $child; | |
161 } | |
162 } | |
163 | |
164 throw new \InvalidArgumentException('The item you passed to remove() was not a child of this component'); | |
165 | |
166 } | |
167 | |
168 } | |
169 | |
170 /** | |
171 * Returns an iterable list of children | |
172 * | |
173 * @return array | |
174 */ | |
175 function children() { | |
176 | |
177 return $this->children; | |
178 | |
179 } | |
180 | |
181 /** | |
182 * This method only returns a list of sub-components. Properties are | |
183 * ignored. | |
184 * | |
185 * @return array | |
186 */ | |
187 function getComponents() { | |
188 | |
189 $result = array(); | |
190 foreach($this->children as $child) { | |
191 if ($child instanceof Component) { | |
192 $result[] = $child; | |
193 } | |
194 } | |
195 | |
196 return $result; | |
197 | |
198 } | |
199 | |
200 /** | |
201 * Returns an array with elements that match the specified name. | |
202 * | |
203 * This function is also aware of MIME-Directory groups (as they appear in | |
204 * vcards). This means that if a property is grouped as "HOME.EMAIL", it | |
205 * will also be returned when searching for just "EMAIL". If you want to | |
206 * search for a property in a specific group, you can select on the entire | |
207 * string ("HOME.EMAIL"). If you want to search on a specific property that | |
208 * has not been assigned a group, specify ".EMAIL". | |
209 * | |
210 * Keys are retained from the 'children' array, which may be confusing in | |
211 * certain cases. | |
212 * | |
213 * @param string $name | |
214 * @return array | |
215 */ | |
216 function select($name) { | |
217 | |
218 $group = null; | |
219 $name = strtoupper($name); | |
220 if (strpos($name,'.')!==false) { | |
221 list($group,$name) = explode('.', $name, 2); | |
222 } | |
223 | |
224 $result = array(); | |
225 foreach($this->children as $key=>$child) { | |
226 | |
227 if ( | |
228 ( | |
229 strtoupper($child->name) === $name | |
230 && (is_null($group) || ( $child instanceof Property && strtoupper($child->group) === $group)) | |
231 ) | |
232 || | |
233 ( | |
234 $name === '' && $child instanceof Property && strtoupper($child->group) === $group | |
235 ) | |
236 ) { | |
237 | |
238 $result[$key] = $child; | |
239 | |
240 } | |
241 } | |
242 | |
243 reset($result); | |
244 return $result; | |
245 | |
246 } | |
247 | |
248 /** | |
249 * Turns the object back into a serialized blob. | |
250 * | |
251 * @return string | |
252 */ | |
253 function serialize() { | |
254 | |
255 $str = "BEGIN:" . $this->name . "\r\n"; | |
256 | |
257 /** | |
258 * Gives a component a 'score' for sorting purposes. | |
259 * | |
260 * This is solely used by the childrenSort method. | |
261 * | |
262 * A higher score means the item will be lower in the list. | |
263 * To avoid score collisions, each "score category" has a reasonable | |
264 * space to accomodate elements. The $key is added to the $score to | |
265 * preserve the original relative order of elements. | |
266 * | |
267 * @param int $key | |
268 * @param array $array | |
269 * @return int | |
270 */ | |
271 $sortScore = function($key, $array) { | |
272 | |
273 if ($array[$key] instanceof Component) { | |
274 | |
275 // We want to encode VTIMEZONE first, this is a personal | |
276 // preference. | |
277 if ($array[$key]->name === 'VTIMEZONE') { | |
278 $score=300000000; | |
279 return $score+$key; | |
280 } else { | |
281 $score=400000000; | |
282 return $score+$key; | |
283 } | |
284 } else { | |
285 // Properties get encoded first | |
286 // VCARD version 4.0 wants the VERSION property to appear first | |
287 if ($array[$key] instanceof Property) { | |
288 if ($array[$key]->name === 'VERSION') { | |
289 $score=100000000; | |
290 return $score+$key; | |
291 } else { | |
292 // All other properties | |
293 $score=200000000; | |
294 return $score+$key; | |
295 } | |
296 } | |
297 } | |
298 | |
299 }; | |
300 | |
301 $tmp = $this->children; | |
302 uksort( | |
303 $this->children, | |
304 function($a, $b) use ($sortScore, $tmp) { | |
305 | |
306 $sA = $sortScore($a, $tmp); | |
307 $sB = $sortScore($b, $tmp); | |
308 | |
309 return $sA - $sB; | |
310 | |
311 } | |
312 ); | |
313 | |
314 foreach($this->children as $child) $str.=$child->serialize(); | |
315 $str.= "END:" . $this->name . "\r\n"; | |
316 | |
317 return $str; | |
318 | |
319 } | |
320 | |
321 /** | |
322 * This method returns an array, with the representation as it should be | |
323 * encoded in json. This is used to create jCard or jCal documents. | |
324 * | |
325 * @return array | |
326 */ | |
327 function jsonSerialize() { | |
328 | |
329 $components = array(); | |
330 $properties = array(); | |
331 | |
332 foreach($this->children as $child) { | |
333 if ($child instanceof Component) { | |
334 $components[] = $child->jsonSerialize(); | |
335 } else { | |
336 $properties[] = $child->jsonSerialize(); | |
337 } | |
338 } | |
339 | |
340 return array( | |
341 strtolower($this->name), | |
342 $properties, | |
343 $components | |
344 ); | |
345 | |
346 } | |
347 | |
348 /** | |
349 * This method should return a list of default property values. | |
350 * | |
351 * @return array | |
352 */ | |
353 protected function getDefaults() { | |
354 | |
355 return array(); | |
356 | |
357 } | |
358 | |
359 /* Magic property accessors {{{ */ | |
360 | |
361 /** | |
362 * Using 'get' you will either get a property or component. | |
363 * | |
364 * If there were no child-elements found with the specified name, | |
365 * null is returned. | |
366 * | |
367 * To use this, this may look something like this: | |
368 * | |
369 * $event = $calendar->VEVENT; | |
370 * | |
371 * @param string $name | |
372 * @return Property | |
373 */ | |
374 function __get($name) { | |
375 | |
376 $matches = $this->select($name); | |
377 if (count($matches)===0) { | |
378 return null; | |
379 } else { | |
380 $firstMatch = current($matches); | |
381 /** @var $firstMatch Property */ | |
382 $firstMatch->setIterator(new ElementList(array_values($matches))); | |
383 return $firstMatch; | |
384 } | |
385 | |
386 } | |
387 | |
388 /** | |
389 * This method checks if a sub-element with the specified name exists. | |
390 * | |
391 * @param string $name | |
392 * @return bool | |
393 */ | |
394 function __isset($name) { | |
395 | |
396 $matches = $this->select($name); | |
397 return count($matches)>0; | |
398 | |
399 } | |
400 | |
401 /** | |
402 * Using the setter method you can add properties or subcomponents | |
403 * | |
404 * You can either pass a Component, Property | |
405 * object, or a string to automatically create a Property. | |
406 * | |
407 * If the item already exists, it will be removed. If you want to add | |
408 * a new item with the same name, always use the add() method. | |
409 * | |
410 * @param string $name | |
411 * @param mixed $value | |
412 * @return void | |
413 */ | |
414 function __set($name, $value) { | |
415 | |
416 $matches = $this->select($name); | |
417 $overWrite = count($matches)?key($matches):null; | |
418 | |
419 if ($value instanceof Component || $value instanceof Property) { | |
420 $value->parent = $this; | |
421 if (!is_null($overWrite)) { | |
422 $this->children[$overWrite] = $value; | |
423 } else { | |
424 $this->children[] = $value; | |
425 } | |
426 } else { | |
427 $property = $this->root->create($name,$value); | |
428 $property->parent = $this; | |
429 if (!is_null($overWrite)) { | |
430 $this->children[$overWrite] = $property; | |
431 } else { | |
432 $this->children[] = $property; | |
433 } | |
434 } | |
435 } | |
436 | |
437 /** | |
438 * Removes all properties and components within this component with the | |
439 * specified name. | |
440 * | |
441 * @param string $name | |
442 * @return void | |
443 */ | |
444 function __unset($name) { | |
445 | |
446 $matches = $this->select($name); | |
447 foreach($matches as $k=>$child) { | |
448 | |
449 unset($this->children[$k]); | |
450 $child->parent = null; | |
451 | |
452 } | |
453 | |
454 } | |
455 | |
456 /* }}} */ | |
457 | |
458 /** | |
459 * This method is automatically called when the object is cloned. | |
460 * Specifically, this will ensure all child elements are also cloned. | |
461 * | |
462 * @return void | |
463 */ | |
464 function __clone() { | |
465 | |
466 foreach($this->children as $key=>$child) { | |
467 $this->children[$key] = clone $child; | |
468 $this->children[$key]->parent = $this; | |
469 } | |
470 | |
471 } | |
472 | |
473 /** | |
474 * A simple list of validation rules. | |
475 * | |
476 * This is simply a list of properties, and how many times they either | |
477 * must or must not appear. | |
478 * | |
479 * Possible values per property: | |
480 * * 0 - Must not appear. | |
481 * * 1 - Must appear exactly once. | |
482 * * + - Must appear at least once. | |
483 * * * - Can appear any number of times. | |
484 * * ? - May appear, but not more than once. | |
485 * | |
486 * It is also possible to specify defaults and severity levels for | |
487 * violating the rule. | |
488 * | |
489 * See the VEVENT implementation for getValidationRules for a more complex | |
490 * example. | |
491 * | |
492 * @var array | |
493 */ | |
494 function getValidationRules() { | |
495 | |
496 return array(); | |
497 | |
498 } | |
499 | |
500 /** | |
501 * Validates the node for correctness. | |
502 * | |
503 * The following options are supported: | |
504 * Node::REPAIR - May attempt to automatically repair the problem. | |
505 * Node::PROFILE_CARDDAV - Validate the vCard for CardDAV purposes. | |
506 * Node::PROFILE_CALDAV - Validate the iCalendar for CalDAV purposes. | |
507 * | |
508 * This method returns an array with detected problems. | |
509 * Every element has the following properties: | |
510 * | |
511 * * level - problem level. | |
512 * * message - A human-readable string describing the issue. | |
513 * * node - A reference to the problematic node. | |
514 * | |
515 * The level means: | |
516 * 1 - The issue was repaired (only happens if REPAIR was turned on). | |
517 * 2 - A warning. | |
518 * 3 - An error. | |
519 * | |
520 * @param int $options | |
521 * @return array | |
522 */ | |
523 function validate($options = 0) { | |
524 | |
525 $rules = $this->getValidationRules(); | |
526 $defaults = $this->getDefaults(); | |
527 | |
528 $propertyCounters = array(); | |
529 | |
530 $messages = array(); | |
531 | |
532 foreach($this->children as $child) { | |
533 $name = strtoupper($child->name); | |
534 if (!isset($propertyCounters[$name])) { | |
535 $propertyCounters[$name] = 1; | |
536 } else { | |
537 $propertyCounters[$name]++; | |
538 } | |
539 $messages = array_merge($messages, $child->validate($options)); | |
540 } | |
541 | |
542 foreach($rules as $propName => $rule) { | |
543 | |
544 switch($rule) { | |
545 case '0' : | |
546 if (isset($propertyCounters[$propName])) { | |
547 $messages[] = array( | |
548 'level' => 3, | |
549 'message' => $propName . ' MUST NOT appear in a ' . $this->name . ' component', | |
550 'node' => $this, | |
551 ); | |
552 } | |
553 break; | |
554 case '1' : | |
555 if (!isset($propertyCounters[$propName]) || $propertyCounters[$propName]!==1) { | |
556 $repaired = false; | |
557 if ($options & self::REPAIR && isset($defaults[$propName])) { | |
558 $this->add($propName, $defaults[$propName]); | |
559 } | |
560 $messages[] = array( | |
561 'level' => $repaired?1:3, | |
562 'message' => $propName . ' MUST appear exactly once in a ' . $this->name . ' component', | |
563 'node' => $this, | |
564 ); | |
565 } | |
566 break; | |
567 case '+' : | |
568 if (!isset($propertyCounters[$propName]) || $propertyCounters[$propName] < 1) { | |
569 $messages[] = array( | |
570 'level' => 3, | |
571 'message' => $propName . ' MUST appear at least once in a ' . $this->name . ' component', | |
572 'node' => $this, | |
573 ); | |
574 } | |
575 break; | |
576 case '*' : | |
577 break; | |
578 case '?' : | |
579 if (isset($propertyCounters[$propName]) && $propertyCounters[$propName] > 1) { | |
580 $messages[] = array( | |
581 'level' => 3, | |
582 'message' => $propName . ' MUST NOT appear more than once in a ' . $this->name . ' component', | |
583 'node' => $this, | |
584 ); | |
585 } | |
586 break; | |
587 | |
588 } | |
589 | |
590 } | |
591 return $messages; | |
592 | |
593 } | |
594 | |
595 } |