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 } |
