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 }