4
|
1 <?php
|
|
2
|
|
3 namespace Sabre\VObject;
|
|
4
|
|
5 /**
|
|
6 * VObject Property
|
|
7 *
|
|
8 * A property in VObject is usually in the form PARAMNAME:paramValue.
|
|
9 * An example is : SUMMARY:Weekly meeting
|
|
10 *
|
|
11 * Properties can also have parameters:
|
|
12 * SUMMARY;LANG=en:Weekly meeting.
|
|
13 *
|
|
14 * Parameters can be accessed using the ArrayAccess interface.
|
|
15 *
|
|
16 * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
|
|
17 * @author Evert Pot (http://evertpot.com/)
|
|
18 * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
|
19 */
|
|
20 class Property extends Node {
|
|
21
|
|
22 /**
|
|
23 * Propertyname
|
|
24 *
|
|
25 * @var string
|
|
26 */
|
|
27 public $name;
|
|
28
|
|
29 /**
|
|
30 * Group name
|
|
31 *
|
|
32 * This may be something like 'HOME' for vcards.
|
|
33 *
|
|
34 * @var string
|
|
35 */
|
|
36 public $group;
|
|
37
|
|
38 /**
|
|
39 * Property parameters
|
|
40 *
|
|
41 * @var array
|
|
42 */
|
|
43 public $parameters = array();
|
|
44
|
|
45 /**
|
|
46 * Property value
|
|
47 *
|
|
48 * @var string
|
|
49 */
|
|
50 public $value;
|
|
51
|
|
52 /**
|
|
53 * If properties are added to this map, they will be automatically mapped
|
|
54 * to their respective classes, if parsed by the reader or constructed with
|
|
55 * the 'create' method.
|
|
56 *
|
|
57 * @var array
|
|
58 */
|
|
59 static public $classMap = array(
|
|
60 'COMPLETED' => 'Sabre\\VObject\\Property\\DateTime',
|
|
61 'CREATED' => 'Sabre\\VObject\\Property\\DateTime',
|
|
62 'DTEND' => 'Sabre\\VObject\\Property\\DateTime',
|
|
63 'DTSTAMP' => 'Sabre\\VObject\\Property\\DateTime',
|
|
64 'DTSTART' => 'Sabre\\VObject\\Property\\DateTime',
|
|
65 'DUE' => 'Sabre\\VObject\\Property\\DateTime',
|
|
66 'EXDATE' => 'Sabre\\VObject\\Property\\MultiDateTime',
|
|
67 'LAST-MODIFIED' => 'Sabre\\VObject\\Property\\DateTime',
|
|
68 'RECURRENCE-ID' => 'Sabre\\VObject\\Property\\DateTime',
|
|
69 'TRIGGER' => 'Sabre\\VObject\\Property\\DateTime',
|
|
70 'N' => 'Sabre\\VObject\\Property\\Compound',
|
|
71 'ORG' => 'Sabre\\VObject\\Property\\Compound',
|
|
72 'ADR' => 'Sabre\\VObject\\Property\\Compound',
|
|
73 'CATEGORIES' => 'Sabre\\VObject\\Property\\Compound',
|
|
74 );
|
|
75
|
|
76 /**
|
|
77 * Creates the new property by name, but in addition will also see if
|
|
78 * there's a class mapped to the property name.
|
|
79 *
|
|
80 * Parameters can be specified with the optional third argument. Parameters
|
|
81 * must be a key->value map of the parameter name, and value. If the value
|
|
82 * is specified as an array, it is assumed that multiple parameters with
|
|
83 * the same name should be added.
|
|
84 *
|
|
85 * @param string $name
|
|
86 * @param string $value
|
|
87 * @param array $parameters
|
|
88 * @return Property
|
|
89 */
|
|
90 static public function create($name, $value = null, array $parameters = array()) {
|
|
91
|
|
92 $name = strtoupper($name);
|
|
93 $shortName = $name;
|
|
94 $group = null;
|
|
95 if (strpos($shortName,'.')!==false) {
|
|
96 list($group, $shortName) = explode('.', $shortName);
|
|
97 }
|
|
98
|
|
99 if (isset(self::$classMap[$shortName])) {
|
|
100 return new self::$classMap[$shortName]($name, $value, $parameters);
|
|
101 } else {
|
|
102 return new self($name, $value, $parameters);
|
|
103 }
|
|
104
|
|
105 }
|
|
106
|
|
107 /**
|
|
108 * Creates a new property object
|
|
109 *
|
|
110 * Parameters can be specified with the optional third argument. Parameters
|
|
111 * must be a key->value map of the parameter name, and value. If the value
|
|
112 * is specified as an array, it is assumed that multiple parameters with
|
|
113 * the same name should be added.
|
|
114 *
|
|
115 * @param string $name
|
|
116 * @param string $value
|
|
117 * @param array $parameters
|
|
118 */
|
|
119 public function __construct($name, $value = null, array $parameters = array()) {
|
|
120
|
|
121 if (!is_scalar($value) && !is_null($value)) {
|
|
122 throw new \InvalidArgumentException('The value argument must be scalar or null');
|
|
123 }
|
|
124
|
|
125 $name = strtoupper($name);
|
|
126 $group = null;
|
|
127 if (strpos($name,'.')!==false) {
|
|
128 list($group, $name) = explode('.', $name);
|
|
129 }
|
|
130 $this->name = $name;
|
|
131 $this->group = $group;
|
|
132 $this->setValue($value);
|
|
133
|
|
134 foreach($parameters as $paramName => $paramValues) {
|
|
135
|
|
136 if (!is_array($paramValues)) {
|
|
137 $paramValues = array($paramValues);
|
|
138 }
|
|
139
|
|
140 foreach($paramValues as $paramValue) {
|
|
141 $this->add($paramName, $paramValue);
|
|
142 }
|
|
143
|
|
144 }
|
|
145
|
|
146 }
|
|
147
|
|
148 /**
|
|
149 * Updates the internal value
|
|
150 *
|
|
151 * @param string $value
|
|
152 * @return void
|
|
153 */
|
|
154 public function setValue($value) {
|
|
155
|
|
156 $this->value = $value;
|
|
157
|
|
158 }
|
|
159
|
|
160 /**
|
|
161 * Returns the internal value
|
|
162 *
|
|
163 * @param string $value
|
|
164 * @return string
|
|
165 */
|
|
166 public function getValue() {
|
|
167
|
|
168 return $this->value;
|
|
169
|
|
170 }
|
|
171
|
|
172 /**
|
|
173 * Turns the object back into a serialized blob.
|
|
174 *
|
|
175 * @return string
|
|
176 */
|
|
177 public function serialize() {
|
|
178
|
|
179 $str = $this->name;
|
|
180 if ($this->group) $str = $this->group . '.' . $this->name;
|
|
181
|
|
182 foreach($this->parameters as $param) {
|
|
183
|
|
184 $str.=';' . $param->serialize();
|
|
185
|
|
186 }
|
|
187
|
|
188 $src = array(
|
|
189 '\\',
|
|
190 "\n",
|
|
191 "\r",
|
|
192 );
|
|
193 $out = array(
|
|
194 '\\\\',
|
|
195 '\n',
|
|
196 '',
|
|
197 );
|
|
198
|
|
199 // avoid double-escaping of \, and \; from Compound properties
|
|
200 if (method_exists($this, 'setParts')) {
|
|
201 $src[] = '\\\\,';
|
|
202 $out[] = '\\,';
|
|
203 $src[] = '\\\\;';
|
|
204 $out[] = '\\;';
|
|
205 }
|
|
206
|
|
207 $str.=':' . str_replace($src, $out, $this->value);
|
|
208
|
|
209 $out = '';
|
|
210 while(strlen($str)>0) {
|
|
211 if (strlen($str)>75) {
|
|
212 $out.= mb_strcut($str,0,75,'utf-8') . "\r\n";
|
|
213 $str = ' ' . mb_strcut($str,75,strlen($str),'utf-8');
|
|
214 } else {
|
|
215 $out.=$str . "\r\n";
|
|
216 $str='';
|
|
217 break;
|
|
218 }
|
|
219 }
|
|
220
|
|
221 return $out;
|
|
222
|
|
223 }
|
|
224
|
|
225 /**
|
|
226 * Adds a new componenten or element
|
|
227 *
|
|
228 * You can call this method with the following syntaxes:
|
|
229 *
|
|
230 * add(Parameter $element)
|
|
231 * add(string $name, $value)
|
|
232 *
|
|
233 * The first version adds an Parameter
|
|
234 * The second adds a property as a string.
|
|
235 *
|
|
236 * @param mixed $item
|
|
237 * @param mixed $itemValue
|
|
238 * @return void
|
|
239 */
|
|
240 public function add($item, $itemValue = null) {
|
|
241
|
|
242 if ($item instanceof Parameter) {
|
|
243 if (!is_null($itemValue)) {
|
|
244 throw new \InvalidArgumentException('The second argument must not be specified, when passing a VObject');
|
|
245 }
|
|
246 $item->parent = $this;
|
|
247 $this->parameters[] = $item;
|
|
248 } elseif(is_string($item)) {
|
|
249
|
|
250 $parameter = new Parameter($item,$itemValue);
|
|
251 $parameter->parent = $this;
|
|
252 $this->parameters[] = $parameter;
|
|
253
|
|
254 } else {
|
|
255
|
|
256 throw new \InvalidArgumentException('The first argument must either be a Node a string');
|
|
257
|
|
258 }
|
|
259
|
|
260 }
|
|
261
|
|
262 /* ArrayAccess interface {{{ */
|
|
263
|
|
264 /**
|
|
265 * Checks if an array element exists
|
|
266 *
|
|
267 * @param mixed $name
|
|
268 * @return bool
|
|
269 */
|
|
270 public function offsetExists($name) {
|
|
271
|
|
272 if (is_int($name)) return parent::offsetExists($name);
|
|
273
|
|
274 $name = strtoupper($name);
|
|
275
|
|
276 foreach($this->parameters as $parameter) {
|
|
277 if ($parameter->name == $name) return true;
|
|
278 }
|
|
279 return false;
|
|
280
|
|
281 }
|
|
282
|
|
283 /**
|
|
284 * Returns a parameter, or parameter list.
|
|
285 *
|
|
286 * @param string $name
|
|
287 * @return Node
|
|
288 */
|
|
289 public function offsetGet($name) {
|
|
290
|
|
291 if (is_int($name)) return parent::offsetGet($name);
|
|
292 $name = strtoupper($name);
|
|
293
|
|
294 $result = array();
|
|
295 foreach($this->parameters as $parameter) {
|
|
296 if ($parameter->name == $name)
|
|
297 $result[] = $parameter;
|
|
298 }
|
|
299
|
|
300 if (count($result)===0) {
|
|
301 return null;
|
|
302 } elseif (count($result)===1) {
|
|
303 return $result[0];
|
|
304 } else {
|
|
305 $result[0]->setIterator(new ElementList($result));
|
|
306 return $result[0];
|
|
307 }
|
|
308
|
|
309 }
|
|
310
|
|
311 /**
|
|
312 * Creates a new parameter
|
|
313 *
|
|
314 * @param string $name
|
|
315 * @param mixed $value
|
|
316 * @return void
|
|
317 */
|
|
318 public function offsetSet($name, $value) {
|
|
319
|
|
320 if (is_int($name)) parent::offsetSet($name, $value);
|
|
321
|
|
322 if (is_scalar($value)) {
|
|
323 if (!is_string($name))
|
|
324 throw new \InvalidArgumentException('A parameter name must be specified. This means you cannot use the $array[]="string" to add parameters.');
|
|
325
|
|
326 $this->offsetUnset($name);
|
|
327 $parameter = new Parameter($name, $value);
|
|
328 $parameter->parent = $this;
|
|
329 $this->parameters[] = $parameter;
|
|
330
|
|
331 } elseif ($value instanceof Parameter) {
|
|
332 if (!is_null($name))
|
|
333 throw new \InvalidArgumentException('Don\'t specify a parameter name if you\'re passing a \\Sabre\\VObject\\Parameter. Add using $array[]=$parameterObject.');
|
|
334
|
|
335 $value->parent = $this;
|
|
336 $this->parameters[] = $value;
|
|
337 } else {
|
|
338 throw new \InvalidArgumentException('You can only add parameters to the property object');
|
|
339 }
|
|
340
|
|
341 }
|
|
342
|
|
343 /**
|
|
344 * Removes one or more parameters with the specified name
|
|
345 *
|
|
346 * @param string $name
|
|
347 * @return void
|
|
348 */
|
|
349 public function offsetUnset($name) {
|
|
350
|
|
351 if (is_int($name)) parent::offsetUnset($name);
|
|
352 $name = strtoupper($name);
|
|
353
|
|
354 foreach($this->parameters as $key=>$parameter) {
|
|
355 if ($parameter->name == $name) {
|
|
356 $parameter->parent = null;
|
|
357 unset($this->parameters[$key]);
|
|
358 }
|
|
359
|
|
360 }
|
|
361
|
|
362 }
|
|
363
|
|
364 /* }}} */
|
|
365
|
|
366 /**
|
|
367 * Called when this object is being cast to a string
|
|
368 *
|
|
369 * @return string
|
|
370 */
|
|
371 public function __toString() {
|
|
372
|
|
373 return (string)$this->value;
|
|
374
|
|
375 }
|
|
376
|
|
377 /**
|
|
378 * This method is automatically called when the object is cloned.
|
|
379 * Specifically, this will ensure all child elements are also cloned.
|
|
380 *
|
|
381 * @return void
|
|
382 */
|
|
383 public function __clone() {
|
|
384
|
|
385 foreach($this->parameters as $key=>$child) {
|
|
386 $this->parameters[$key] = clone $child;
|
|
387 $this->parameters[$key]->parent = $this;
|
|
388 }
|
|
389
|
|
390 }
|
|
391
|
|
392 /**
|
|
393 * Validates the node for correctness.
|
|
394 *
|
|
395 * The following options are supported:
|
|
396 * - Node::REPAIR - If something is broken, and automatic repair may
|
|
397 * be attempted.
|
|
398 *
|
|
399 * An array is returned with warnings.
|
|
400 *
|
|
401 * Every item in the array has the following properties:
|
|
402 * * level - (number between 1 and 3 with severity information)
|
|
403 * * message - (human readable message)
|
|
404 * * node - (reference to the offending node)
|
|
405 *
|
|
406 * @param int $options
|
|
407 * @return array
|
|
408 */
|
|
409 public function validate($options = 0) {
|
|
410
|
|
411 $warnings = array();
|
|
412
|
|
413 // Checking if our value is UTF-8
|
|
414 if (!StringUtil::isUTF8($this->value)) {
|
|
415 $warnings[] = array(
|
|
416 'level' => 1,
|
|
417 'message' => 'Property is not valid UTF-8!',
|
|
418 'node' => $this,
|
|
419 );
|
|
420 if ($options & self::REPAIR) {
|
|
421 $this->value = StringUtil::convertToUTF8($this->value);
|
|
422 }
|
|
423 }
|
|
424
|
|
425 // Checking if the propertyname does not contain any invalid bytes.
|
|
426 if (!preg_match('/^([A-Z0-9-]+)$/', $this->name)) {
|
|
427 $warnings[] = array(
|
|
428 'level' => 1,
|
|
429 'message' => 'The propertyname: ' . $this->name . ' contains invalid characters. Only A-Z, 0-9 and - are allowed',
|
|
430 'node' => $this,
|
|
431 );
|
|
432 if ($options & self::REPAIR) {
|
|
433 // Uppercasing and converting underscores to dashes.
|
|
434 $this->name = strtoupper(
|
|
435 str_replace('_', '-', $this->name)
|
|
436 );
|
|
437 // Removing every other invalid character
|
|
438 $this->name = preg_replace('/([^A-Z0-9-])/u', '', $this->name);
|
|
439
|
|
440 }
|
|
441
|
|
442 }
|
|
443
|
|
444 // Validating inner parameters
|
|
445 foreach($this->parameters as $param) {
|
|
446 $warnings = array_merge($warnings, $param->validate($options));
|
|
447 }
|
|
448
|
|
449 return $warnings;
|
|
450
|
|
451 }
|
|
452
|
|
453 }
|