1<?php 2 3namespace Sabre\VObject; 4 5use ArrayIterator; 6use Sabre\Xml; 7 8/** 9 * VObject Parameter. 10 * 11 * This class represents a parameter. A parameter is always tied to a property. 12 * In the case of: 13 * DTSTART;VALUE=DATE:20101108 14 * VALUE=DATE would be the parameter name and value. 15 * 16 * @copyright Copyright (C) fruux GmbH (https://fruux.com/) 17 * @author Evert Pot (http://evertpot.com/) 18 * @license http://sabre.io/license/ Modified BSD License 19 */ 20class Parameter extends Node { 21 22 /** 23 * Parameter name. 24 * 25 * @var string 26 */ 27 public $name; 28 29 /** 30 * vCard 2.1 allows parameters to be encoded without a name. 31 * 32 * We can deduce the parameter name based on it's value. 33 * 34 * @var bool 35 */ 36 public $noName = false; 37 38 /** 39 * Parameter value. 40 * 41 * @var string 42 */ 43 protected $value; 44 45 /** 46 * Sets up the object. 47 * 48 * It's recommended to use the create:: factory method instead. 49 * 50 * @param string $name 51 * @param string $value 52 */ 53 function __construct(Document $root, $name, $value = null) { 54 55 $this->name = strtoupper($name); 56 $this->root = $root; 57 if (is_null($name)) { 58 $this->noName = true; 59 $this->name = static::guessParameterNameByValue($value); 60 } 61 62 // If guessParameterNameByValue() returns an empty string 63 // above, we're actually dealing with a parameter that has no value. 64 // In that case we have to move the value to the name. 65 if ($this->name === '') { 66 $this->noName = false; 67 $this->name = strtoupper($value); 68 } else { 69 $this->setValue($value); 70 } 71 72 } 73 74 /** 75 * Try to guess property name by value, can be used for vCard 2.1 nameless parameters. 76 * 77 * Figuring out what the name should have been. Note that a ton of 78 * these are rather silly in 2014 and would probably rarely be 79 * used, but we like to be complete. 80 * 81 * @param string $value 82 * 83 * @return string 84 */ 85 static function guessParameterNameByValue($value) { 86 switch (strtoupper($value)) { 87 88 // Encodings 89 case '7-BIT' : 90 case 'QUOTED-PRINTABLE' : 91 case 'BASE64' : 92 $name = 'ENCODING'; 93 break; 94 95 // Common types 96 case 'WORK' : 97 case 'HOME' : 98 case 'PREF' : 99 100 // Delivery Label Type 101 case 'DOM' : 102 case 'INTL' : 103 case 'POSTAL' : 104 case 'PARCEL' : 105 106 // Telephone types 107 case 'VOICE' : 108 case 'FAX' : 109 case 'MSG' : 110 case 'CELL' : 111 case 'PAGER' : 112 case 'BBS' : 113 case 'MODEM' : 114 case 'CAR' : 115 case 'ISDN' : 116 case 'VIDEO' : 117 118 // EMAIL types (lol) 119 case 'AOL' : 120 case 'APPLELINK' : 121 case 'ATTMAIL' : 122 case 'CIS' : 123 case 'EWORLD' : 124 case 'INTERNET' : 125 case 'IBMMAIL' : 126 case 'MCIMAIL' : 127 case 'POWERSHARE' : 128 case 'PRODIGY' : 129 case 'TLX' : 130 case 'X400' : 131 132 // Photo / Logo format types 133 case 'GIF' : 134 case 'CGM' : 135 case 'WMF' : 136 case 'BMP' : 137 case 'DIB' : 138 case 'PICT' : 139 case 'TIFF' : 140 case 'PDF' : 141 case 'PS' : 142 case 'JPEG' : 143 case 'MPEG' : 144 case 'MPEG2' : 145 case 'AVI' : 146 case 'QTIME' : 147 148 // Sound Digital Audio Type 149 case 'WAVE' : 150 case 'PCM' : 151 case 'AIFF' : 152 153 // Key types 154 case 'X509' : 155 case 'PGP' : 156 $name = 'TYPE'; 157 break; 158 159 // Value types 160 case 'INLINE' : 161 case 'URL' : 162 case 'CONTENT-ID' : 163 case 'CID' : 164 $name = 'VALUE'; 165 break; 166 167 default: 168 $name = ''; 169 } 170 171 return $name; 172 } 173 174 /** 175 * Updates the current value. 176 * 177 * This may be either a single, or multiple strings in an array. 178 * 179 * @param string|array $value 180 * 181 * @return void 182 */ 183 function setValue($value) { 184 185 $this->value = $value; 186 187 } 188 189 /** 190 * Returns the current value. 191 * 192 * This method will always return a string, or null. If there were multiple 193 * values, it will automatically concatenate them (separated by comma). 194 * 195 * @return string|null 196 */ 197 function getValue() { 198 199 if (is_array($this->value)) { 200 return implode(',', $this->value); 201 } else { 202 return $this->value; 203 } 204 205 } 206 207 /** 208 * Sets multiple values for this parameter. 209 * 210 * @param array $value 211 * 212 * @return void 213 */ 214 function setParts(array $value) { 215 216 $this->value = $value; 217 218 } 219 220 /** 221 * Returns all values for this parameter. 222 * 223 * If there were no values, an empty array will be returned. 224 * 225 * @return array 226 */ 227 function getParts() { 228 229 if (is_array($this->value)) { 230 return $this->value; 231 } elseif (is_null($this->value)) { 232 return []; 233 } else { 234 return [$this->value]; 235 } 236 237 } 238 239 /** 240 * Adds a value to this parameter. 241 * 242 * If the argument is specified as an array, all items will be added to the 243 * parameter value list. 244 * 245 * @param string|array $part 246 * 247 * @return void 248 */ 249 function addValue($part) { 250 251 if (is_null($this->value)) { 252 $this->value = $part; 253 } else { 254 $this->value = array_merge((array)$this->value, (array)$part); 255 } 256 257 } 258 259 /** 260 * Checks if this parameter contains the specified value. 261 * 262 * This is a case-insensitive match. It makes sense to call this for for 263 * instance the TYPE parameter, to see if it contains a keyword such as 264 * 'WORK' or 'FAX'. 265 * 266 * @param string $value 267 * 268 * @return bool 269 */ 270 function has($value) { 271 272 return in_array( 273 strtolower($value), 274 array_map('strtolower', (array)$this->value) 275 ); 276 277 } 278 279 /** 280 * Turns the object back into a serialized blob. 281 * 282 * @return string 283 */ 284 function serialize() { 285 286 $value = $this->getParts(); 287 288 if (count($value) === 0) { 289 return $this->name . '='; 290 } 291 292 if ($this->root->getDocumentType() === Document::VCARD21 && $this->noName) { 293 294 return implode(';', $value); 295 296 } 297 298 return $this->name . '=' . array_reduce( 299 $value, 300 function($out, $item) { 301 302 if (!is_null($out)) $out .= ','; 303 304 // If there's no special characters in the string, we'll use the simple 305 // format. 306 // 307 // The list of special characters is defined as: 308 // 309 // Any character except CONTROL, DQUOTE, ";", ":", "," 310 // 311 // by the iCalendar spec: 312 // https://tools.ietf.org/html/rfc5545#section-3.1 313 // 314 // And we add ^ to that because of: 315 // https://tools.ietf.org/html/rfc6868 316 // 317 // But we've found that iCal (7.0, shipped with OSX 10.9) 318 // severaly trips on + characters not being quoted, so we 319 // added + as well. 320 if (!preg_match('#(?: [\n":;\^,\+] )#x', $item)) { 321 return $out . $item; 322 } else { 323 // Enclosing in double-quotes, and using RFC6868 for encoding any 324 // special characters 325 $out .= '"' . strtr( 326 $item, 327 [ 328 '^' => '^^', 329 "\n" => '^n', 330 '"' => '^\'', 331 ] 332 ) . '"'; 333 return $out; 334 } 335 336 } 337 ); 338 339 } 340 341 /** 342 * This method returns an array, with the representation as it should be 343 * encoded in JSON. This is used to create jCard or jCal documents. 344 * 345 * @return array 346 */ 347 function jsonSerialize() { 348 349 return $this->value; 350 351 } 352 353 /** 354 * This method serializes the data into XML. This is used to create xCard or 355 * xCal documents. 356 * 357 * @param Xml\Writer $writer XML writer. 358 * 359 * @return void 360 */ 361 function xmlSerialize(Xml\Writer $writer) { 362 363 foreach (explode(',', $this->value) as $value) { 364 $writer->writeElement('text', $value); 365 } 366 367 } 368 369 /** 370 * Called when this object is being cast to a string. 371 * 372 * @return string 373 */ 374 function __toString() { 375 376 return (string)$this->getValue(); 377 378 } 379 380 /** 381 * Returns the iterator for this object. 382 * 383 * @return ElementList 384 */ 385 function getIterator() { 386 387 if (!is_null($this->iterator)) 388 return $this->iterator; 389 390 return $this->iterator = new ArrayIterator((array)$this->value); 391 392 } 393 394} 395