name = strtoupper($name); $this->root = $root; if (is_null($name)) { $this->noName = true; $this->name = static::guessParameterNameByValue($value); } // If guessParameterNameByValue() returns an empty string // above, we're actually dealing with a parameter that has no value. // In that case we have to move the value to the name. if ($this->name === '') { $this->noName = false; $this->name = strtoupper($value); } else { $this->setValue($value); } } /** * Try to guess property name by value, can be used for vCard 2.1 nameless parameters. * * Figuring out what the name should have been. Note that a ton of * these are rather silly in 2014 and would probably rarely be * used, but we like to be complete. * * @param string $value * * @return string */ static function guessParameterNameByValue($value) { switch (strtoupper($value)) { // Encodings case '7-BIT' : case 'QUOTED-PRINTABLE' : case 'BASE64' : $name = 'ENCODING'; break; // Common types case 'WORK' : case 'HOME' : case 'PREF' : // Delivery Label Type case 'DOM' : case 'INTL' : case 'POSTAL' : case 'PARCEL' : // Telephone types case 'VOICE' : case 'FAX' : case 'MSG' : case 'CELL' : case 'PAGER' : case 'BBS' : case 'MODEM' : case 'CAR' : case 'ISDN' : case 'VIDEO' : // EMAIL types (lol) case 'AOL' : case 'APPLELINK' : case 'ATTMAIL' : case 'CIS' : case 'EWORLD' : case 'INTERNET' : case 'IBMMAIL' : case 'MCIMAIL' : case 'POWERSHARE' : case 'PRODIGY' : case 'TLX' : case 'X400' : // Photo / Logo format types case 'GIF' : case 'CGM' : case 'WMF' : case 'BMP' : case 'DIB' : case 'PICT' : case 'TIFF' : case 'PDF' : case 'PS' : case 'JPEG' : case 'MPEG' : case 'MPEG2' : case 'AVI' : case 'QTIME' : // Sound Digital Audio Type case 'WAVE' : case 'PCM' : case 'AIFF' : // Key types case 'X509' : case 'PGP' : $name = 'TYPE'; break; // Value types case 'INLINE' : case 'URL' : case 'CONTENT-ID' : case 'CID' : $name = 'VALUE'; break; default: $name = ''; } return $name; } /** * Updates the current value. * * This may be either a single, or multiple strings in an array. * * @param string|array $value * * @return void */ function setValue($value) { $this->value = $value; } /** * Returns the current value. * * This method will always return a string, or null. If there were multiple * values, it will automatically concatenate them (separated by comma). * * @return string|null */ function getValue() { if (is_array($this->value)) { return implode(',', $this->value); } else { return $this->value; } } /** * Sets multiple values for this parameter. * * @param array $value * * @return void */ function setParts(array $value) { $this->value = $value; } /** * Returns all values for this parameter. * * If there were no values, an empty array will be returned. * * @return array */ function getParts() { if (is_array($this->value)) { return $this->value; } elseif (is_null($this->value)) { return []; } else { return [$this->value]; } } /** * Adds a value to this parameter. * * If the argument is specified as an array, all items will be added to the * parameter value list. * * @param string|array $part * * @return void */ function addValue($part) { if (is_null($this->value)) { $this->value = $part; } else { $this->value = array_merge((array)$this->value, (array)$part); } } /** * Checks if this parameter contains the specified value. * * This is a case-insensitive match. It makes sense to call this for for * instance the TYPE parameter, to see if it contains a keyword such as * 'WORK' or 'FAX'. * * @param string $value * * @return bool */ function has($value) { return in_array( strtolower($value), array_map('strtolower', (array)$this->value) ); } /** * Turns the object back into a serialized blob. * * @return string */ function serialize() { $value = $this->getParts(); if (count($value) === 0) { return $this->name . '='; } if ($this->root->getDocumentType() === Document::VCARD21 && $this->noName) { return implode(';', $value); } return $this->name . '=' . array_reduce( $value, function($out, $item) { if (!is_null($out)) $out .= ','; // If there's no special characters in the string, we'll use the simple // format. // // The list of special characters is defined as: // // Any character except CONTROL, DQUOTE, ";", ":", "," // // by the iCalendar spec: // https://tools.ietf.org/html/rfc5545#section-3.1 // // And we add ^ to that because of: // https://tools.ietf.org/html/rfc6868 // // But we've found that iCal (7.0, shipped with OSX 10.9) // severaly trips on + characters not being quoted, so we // added + as well. if (!preg_match('#(?: [\n":;\^,\+] )#x', $item)) { return $out . $item; } else { // Enclosing in double-quotes, and using RFC6868 for encoding any // special characters $out .= '"' . strtr( $item, [ '^' => '^^', "\n" => '^n', '"' => '^\'', ] ) . '"'; return $out; } } ); } /** * This method returns an array, with the representation as it should be * encoded in JSON. This is used to create jCard or jCal documents. * * @return array */ function jsonSerialize() { return $this->value; } /** * This method serializes the data into XML. This is used to create xCard or * xCal documents. * * @param Xml\Writer $writer XML writer. * * @return void */ function xmlSerialize(Xml\Writer $writer) { foreach (explode(',', $this->value) as $value) { $writer->writeElement('text', $value); } } /** * Called when this object is being cast to a string. * * @return string */ function __toString() { return (string)$this->getValue(); } /** * Returns the iterator for this object. * * @return ElementList */ function getIterator() { if (!is_null($this->iterator)) return $this->iterator; return $this->iterator = new ArrayIterator((array)$this->value); } }