5, 'ADR' => 7, ]; /** * Creates the property. * * You can specify the parameters either in key=>value syntax, in which case * parameters will automatically be created, or you can just pass a list of * Parameter objects. * * @param Component $root The root document * @param string $name * @param string|array|null $value * @param array $parameters List of parameters * @param string $group The vcard property group * * @return void */ function __construct(Component $root, $name, $value = null, array $parameters = [], $group = null) { // There's two types of multi-valued text properties: // 1. multivalue properties. // 2. structured value properties // // The former is always separated by a comma, the latter by semi-colon. if (in_array($name, $this->structuredValues)) { $this->delimiter = ';'; } parent::__construct($root, $name, $value, $parameters, $group); } /** * Sets a raw value coming from a mimedir (iCalendar/vCard) file. * * This has been 'unfolded', so only 1 line will be passed. Unescaping is * not yet done, but parameters are not included. * * @param string $val * * @return void */ function setRawMimeDirValue($val) { $this->setValue(MimeDir::unescapeValue($val, $this->delimiter)); } /** * Sets the value as a quoted-printable encoded string. * * @param string $val * * @return void */ function setQuotedPrintableValue($val) { $val = quoted_printable_decode($val); // Quoted printable only appears in vCard 2.1, and the only character // that may be escaped there is ;. So we are simply splitting on just // that. // // We also don't have to unescape \\, so all we need to look for is a ; // that's not preceeded with a \. $regex = '# (?setValue($matches); } /** * Returns a raw mime-dir representation of the value. * * @return string */ function getRawMimeDirValue() { $val = $this->getParts(); if (isset($this->minimumPropertyValues[$this->name])) { $val = array_pad($val, $this->minimumPropertyValues[$this->name], ''); } foreach ($val as &$item) { if (!is_array($item)) { $item = [$item]; } foreach ($item as &$subItem) { $subItem = strtr( $subItem, [ '\\' => '\\\\', ';' => '\;', ',' => '\,', "\n" => '\n', "\r" => "", ] ); } $item = implode(',', $item); } return implode($this->delimiter, $val); } /** * Returns the value, in the format it should be encoded for json. * * This method must always return an array. * * @return array */ function getJsonValue() { // Structured text values should always be returned as a single // array-item. Multi-value text should be returned as multiple items in // the top-array. if (in_array($this->name, $this->structuredValues)) { return [$this->getParts()]; } return $this->getParts(); } /** * Returns the type of value. * * This corresponds to the VALUE= parameter. Every property also has a * 'default' valueType. * * @return string */ function getValueType() { return 'TEXT'; } /** * Turns the object back into a serialized blob. * * @return string */ function serialize() { // We need to kick in a special type of encoding, if it's a 2.1 vcard. if ($this->root->getDocumentType() !== Document::VCARD21) { return parent::serialize(); } $val = $this->getParts(); if (isset($this->minimumPropertyValues[$this->name])) { $val = array_pad($val, $this->minimumPropertyValues[$this->name], ''); } // Imploding multiple parts into a single value, and splitting the // values with ;. if (count($val) > 1) { foreach ($val as $k => $v) { $val[$k] = str_replace(';', '\;', $v); } $val = implode(';', $val); } else { $val = $val[0]; } $str = $this->name; if ($this->group) $str = $this->group . '.' . $this->name; foreach ($this->parameters as $param) { if ($param->getValue() === 'QUOTED-PRINTABLE') { continue; } $str .= ';' . $param->serialize(); } // If the resulting value contains a \n, we must encode it as // quoted-printable. if (strpos($val, "\n") !== false) { $str .= ';ENCODING=QUOTED-PRINTABLE:'; $lastLine = $str; $out = null; // The PHP built-in quoted-printable-encode does not correctly // encode newlines for us. Specifically, the \r\n sequence must in // vcards be encoded as =0D=OA and we must insert soft-newlines // every 75 bytes. for ($ii = 0;$ii < strlen($val);$ii++) { $ord = ord($val[$ii]); // These characters are encoded as themselves. if ($ord >= 32 && $ord <= 126) { $lastLine .= $val[$ii]; } else { $lastLine .= '=' . strtoupper(bin2hex($val[$ii])); } if (strlen($lastLine) >= 75) { // Soft line break $out .= $lastLine . "=\r\n "; $lastLine = null; } } if (!is_null($lastLine)) $out .= $lastLine . "\r\n"; return $out; } else { $str .= ':' . $val; $out = ''; while (strlen($str) > 0) { if (strlen($str) > 75) { $part = mb_strcut($str, 0, 75, 'utf-8'); $out .= $part . "\r\n"; $str = ' ' . substr($str, strlen($part)); } else { $out .= $str . "\r\n"; $str = ''; break; } } return $out; } } /** * This method serializes only the value of a property. This is used to * create xCard or xCal documents. * * @param Xml\Writer $writer XML writer. * * @return void */ protected function xmlSerializeValue(Xml\Writer $writer) { $values = $this->getParts(); $map = function($items) use ($values, $writer) { foreach ($items as $i => $item) { $writer->writeElement( $item, !empty($values[$i]) ? $values[$i] : null ); } }; switch ($this->name) { // Special-casing the REQUEST-STATUS property. // // See: // http://tools.ietf.org/html/rfc6321#section-3.4.1.3 case 'REQUEST-STATUS': $writer->writeElement('code', $values[0]); $writer->writeElement('description', $values[1]); if (isset($values[2])) { $writer->writeElement('data', $values[2]); } break; case 'N': $map([ 'surname', 'given', 'additional', 'prefix', 'suffix' ]); break; case 'GENDER': $map([ 'sex', 'text' ]); break; case 'ADR': $map([ 'pobox', 'ext', 'street', 'locality', 'region', 'code', 'country' ]); break; case 'CLIENTPIDMAP': $map([ 'sourceid', 'uri' ]); break; default: parent::xmlSerializeValue($writer); } } /** * Validates the node for correctness. * * The following options are supported: * - Node::REPAIR - If something is broken, and automatic repair may * be attempted. * * An array is returned with warnings. * * Every item in the array has the following properties: * * level - (number between 1 and 3 with severity information) * * message - (human readable message) * * node - (reference to the offending node) * * @param int $options * * @return array */ function validate($options = 0) { $warnings = parent::validate($options); if (isset($this->minimumPropertyValues[$this->name])) { $minimum = $this->minimumPropertyValues[$this->name]; $parts = $this->getParts(); if (count($parts) < $minimum) { $warnings[] = [ 'level' => $options & self::REPAIR ? 1 : 3, 'message' => 'The ' . $this->name . ' property must have at least ' . $minimum . ' values. It only has ' . count($parts), 'node' => $this, ]; if ($options & self::REPAIR) { $parts = array_pad($parts, $minimum, ''); $this->setParts($parts); } } } return $warnings; } }