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