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