1*a1a3b679SAndreas Boehler<?php 2*a1a3b679SAndreas Boehler 3*a1a3b679SAndreas Boehlernamespace Sabre\VObject\Property; 4*a1a3b679SAndreas Boehler 5*a1a3b679SAndreas Boehleruse 6*a1a3b679SAndreas Boehler Sabre\VObject\Property, 7*a1a3b679SAndreas Boehler Sabre\VObject\Component, 8*a1a3b679SAndreas Boehler Sabre\VObject\Parser\MimeDir, 9*a1a3b679SAndreas Boehler Sabre\VObject\Document; 10*a1a3b679SAndreas Boehler 11*a1a3b679SAndreas Boehler/** 12*a1a3b679SAndreas Boehler * Text property 13*a1a3b679SAndreas Boehler * 14*a1a3b679SAndreas Boehler * This object represents TEXT values. 15*a1a3b679SAndreas Boehler * 16*a1a3b679SAndreas Boehler * @copyright Copyright (C) 2011-2015 fruux GmbH (https://fruux.com/). 17*a1a3b679SAndreas Boehler * @author Evert Pot (http://evertpot.com/) 18*a1a3b679SAndreas Boehler * @license http://sabre.io/license/ Modified BSD License 19*a1a3b679SAndreas Boehler */ 20*a1a3b679SAndreas Boehlerclass Text extends Property { 21*a1a3b679SAndreas Boehler 22*a1a3b679SAndreas Boehler /** 23*a1a3b679SAndreas Boehler * In case this is a multi-value property. This string will be used as a 24*a1a3b679SAndreas Boehler * delimiter. 25*a1a3b679SAndreas Boehler * 26*a1a3b679SAndreas Boehler * @var string 27*a1a3b679SAndreas Boehler */ 28*a1a3b679SAndreas Boehler public $delimiter = ','; 29*a1a3b679SAndreas Boehler 30*a1a3b679SAndreas Boehler /** 31*a1a3b679SAndreas Boehler * List of properties that are considered 'structured'. 32*a1a3b679SAndreas Boehler * 33*a1a3b679SAndreas Boehler * @var array 34*a1a3b679SAndreas Boehler */ 35*a1a3b679SAndreas Boehler protected $structuredValues = array( 36*a1a3b679SAndreas Boehler // vCard 37*a1a3b679SAndreas Boehler 'N', 38*a1a3b679SAndreas Boehler 'ADR', 39*a1a3b679SAndreas Boehler 'ORG', 40*a1a3b679SAndreas Boehler 'GENDER', 41*a1a3b679SAndreas Boehler 42*a1a3b679SAndreas Boehler // iCalendar 43*a1a3b679SAndreas Boehler 'REQUEST-STATUS', 44*a1a3b679SAndreas Boehler ); 45*a1a3b679SAndreas Boehler 46*a1a3b679SAndreas Boehler /** 47*a1a3b679SAndreas Boehler * Some text components have a minimum number of components. 48*a1a3b679SAndreas Boehler * 49*a1a3b679SAndreas Boehler * N must for instance be represented as 5 components, separated by ;, even 50*a1a3b679SAndreas Boehler * if the last few components are unused. 51*a1a3b679SAndreas Boehler * 52*a1a3b679SAndreas Boehler * @var array 53*a1a3b679SAndreas Boehler */ 54*a1a3b679SAndreas Boehler protected $minimumPropertyValues = array( 55*a1a3b679SAndreas Boehler 'N' => 5, 56*a1a3b679SAndreas Boehler 'ADR' => 7, 57*a1a3b679SAndreas Boehler ); 58*a1a3b679SAndreas Boehler 59*a1a3b679SAndreas Boehler /** 60*a1a3b679SAndreas Boehler * Creates the property. 61*a1a3b679SAndreas Boehler * 62*a1a3b679SAndreas Boehler * You can specify the parameters either in key=>value syntax, in which case 63*a1a3b679SAndreas Boehler * parameters will automatically be created, or you can just pass a list of 64*a1a3b679SAndreas Boehler * Parameter objects. 65*a1a3b679SAndreas Boehler * 66*a1a3b679SAndreas Boehler * @param Component $root The root document 67*a1a3b679SAndreas Boehler * @param string $name 68*a1a3b679SAndreas Boehler * @param string|array|null $value 69*a1a3b679SAndreas Boehler * @param array $parameters List of parameters 70*a1a3b679SAndreas Boehler * @param string $group The vcard property group 71*a1a3b679SAndreas Boehler * @return void 72*a1a3b679SAndreas Boehler */ 73*a1a3b679SAndreas Boehler public function __construct(Component $root, $name, $value = null, array $parameters = array(), $group = null) { 74*a1a3b679SAndreas Boehler 75*a1a3b679SAndreas Boehler // There's two types of multi-valued text properties: 76*a1a3b679SAndreas Boehler // 1. multivalue properties. 77*a1a3b679SAndreas Boehler // 2. structured value properties 78*a1a3b679SAndreas Boehler // 79*a1a3b679SAndreas Boehler // The former is always separated by a comma, the latter by semi-colon. 80*a1a3b679SAndreas Boehler if (in_array($name, $this->structuredValues)) { 81*a1a3b679SAndreas Boehler $this->delimiter = ';'; 82*a1a3b679SAndreas Boehler } 83*a1a3b679SAndreas Boehler 84*a1a3b679SAndreas Boehler parent::__construct($root, $name, $value, $parameters, $group); 85*a1a3b679SAndreas Boehler 86*a1a3b679SAndreas Boehler } 87*a1a3b679SAndreas Boehler 88*a1a3b679SAndreas Boehler 89*a1a3b679SAndreas Boehler /** 90*a1a3b679SAndreas Boehler * Sets a raw value coming from a mimedir (iCalendar/vCard) file. 91*a1a3b679SAndreas Boehler * 92*a1a3b679SAndreas Boehler * This has been 'unfolded', so only 1 line will be passed. Unescaping is 93*a1a3b679SAndreas Boehler * not yet done, but parameters are not included. 94*a1a3b679SAndreas Boehler * 95*a1a3b679SAndreas Boehler * @param string $val 96*a1a3b679SAndreas Boehler * @return void 97*a1a3b679SAndreas Boehler */ 98*a1a3b679SAndreas Boehler public function setRawMimeDirValue($val) { 99*a1a3b679SAndreas Boehler 100*a1a3b679SAndreas Boehler $this->setValue(MimeDir::unescapeValue($val, $this->delimiter)); 101*a1a3b679SAndreas Boehler 102*a1a3b679SAndreas Boehler } 103*a1a3b679SAndreas Boehler 104*a1a3b679SAndreas Boehler /** 105*a1a3b679SAndreas Boehler * Sets the value as a quoted-printable encoded string. 106*a1a3b679SAndreas Boehler * 107*a1a3b679SAndreas Boehler * @param string $val 108*a1a3b679SAndreas Boehler * @return void 109*a1a3b679SAndreas Boehler */ 110*a1a3b679SAndreas Boehler public function setQuotedPrintableValue($val) { 111*a1a3b679SAndreas Boehler 112*a1a3b679SAndreas Boehler $val = quoted_printable_decode($val); 113*a1a3b679SAndreas Boehler 114*a1a3b679SAndreas Boehler // Quoted printable only appears in vCard 2.1, and the only character 115*a1a3b679SAndreas Boehler // that may be escaped there is ;. So we are simply splitting on just 116*a1a3b679SAndreas Boehler // that. 117*a1a3b679SAndreas Boehler // 118*a1a3b679SAndreas Boehler // We also don't have to unescape \\, so all we need to look for is a ; 119*a1a3b679SAndreas Boehler // that's not preceeded with a \. 120*a1a3b679SAndreas Boehler $regex = '# (?<!\\\\) ; #x'; 121*a1a3b679SAndreas Boehler $matches = preg_split($regex, $val); 122*a1a3b679SAndreas Boehler $this->setValue($matches); 123*a1a3b679SAndreas Boehler 124*a1a3b679SAndreas Boehler } 125*a1a3b679SAndreas Boehler 126*a1a3b679SAndreas Boehler /** 127*a1a3b679SAndreas Boehler * Returns a raw mime-dir representation of the value. 128*a1a3b679SAndreas Boehler * 129*a1a3b679SAndreas Boehler * @return string 130*a1a3b679SAndreas Boehler */ 131*a1a3b679SAndreas Boehler public function getRawMimeDirValue() { 132*a1a3b679SAndreas Boehler 133*a1a3b679SAndreas Boehler $val = $this->getParts(); 134*a1a3b679SAndreas Boehler 135*a1a3b679SAndreas Boehler if (isset($this->minimumPropertyValues[$this->name])) { 136*a1a3b679SAndreas Boehler $val = array_pad($val, $this->minimumPropertyValues[$this->name], ''); 137*a1a3b679SAndreas Boehler } 138*a1a3b679SAndreas Boehler 139*a1a3b679SAndreas Boehler foreach($val as &$item) { 140*a1a3b679SAndreas Boehler 141*a1a3b679SAndreas Boehler if (!is_array($item)) { 142*a1a3b679SAndreas Boehler $item = array($item); 143*a1a3b679SAndreas Boehler } 144*a1a3b679SAndreas Boehler 145*a1a3b679SAndreas Boehler foreach($item as &$subItem) { 146*a1a3b679SAndreas Boehler $subItem = strtr( 147*a1a3b679SAndreas Boehler $subItem, 148*a1a3b679SAndreas Boehler array( 149*a1a3b679SAndreas Boehler '\\' => '\\\\', 150*a1a3b679SAndreas Boehler ';' => '\;', 151*a1a3b679SAndreas Boehler ',' => '\,', 152*a1a3b679SAndreas Boehler "\n" => '\n', 153*a1a3b679SAndreas Boehler "\r" => "", 154*a1a3b679SAndreas Boehler ) 155*a1a3b679SAndreas Boehler ); 156*a1a3b679SAndreas Boehler } 157*a1a3b679SAndreas Boehler $item = implode(',', $item); 158*a1a3b679SAndreas Boehler 159*a1a3b679SAndreas Boehler } 160*a1a3b679SAndreas Boehler 161*a1a3b679SAndreas Boehler return implode($this->delimiter, $val); 162*a1a3b679SAndreas Boehler 163*a1a3b679SAndreas Boehler } 164*a1a3b679SAndreas Boehler 165*a1a3b679SAndreas Boehler /** 166*a1a3b679SAndreas Boehler * Returns the value, in the format it should be encoded for json. 167*a1a3b679SAndreas Boehler * 168*a1a3b679SAndreas Boehler * This method must always return an array. 169*a1a3b679SAndreas Boehler * 170*a1a3b679SAndreas Boehler * @return array 171*a1a3b679SAndreas Boehler */ 172*a1a3b679SAndreas Boehler public function getJsonValue() { 173*a1a3b679SAndreas Boehler 174*a1a3b679SAndreas Boehler // Structured text values should always be returned as a single 175*a1a3b679SAndreas Boehler // array-item. Multi-value text should be returned as multiple items in 176*a1a3b679SAndreas Boehler // the top-array. 177*a1a3b679SAndreas Boehler if (in_array($this->name, $this->structuredValues)) { 178*a1a3b679SAndreas Boehler return array($this->getParts()); 179*a1a3b679SAndreas Boehler } else { 180*a1a3b679SAndreas Boehler return $this->getParts(); 181*a1a3b679SAndreas Boehler } 182*a1a3b679SAndreas Boehler 183*a1a3b679SAndreas Boehler } 184*a1a3b679SAndreas Boehler 185*a1a3b679SAndreas Boehler /** 186*a1a3b679SAndreas Boehler * Returns the type of value. 187*a1a3b679SAndreas Boehler * 188*a1a3b679SAndreas Boehler * This corresponds to the VALUE= parameter. Every property also has a 189*a1a3b679SAndreas Boehler * 'default' valueType. 190*a1a3b679SAndreas Boehler * 191*a1a3b679SAndreas Boehler * @return string 192*a1a3b679SAndreas Boehler */ 193*a1a3b679SAndreas Boehler public function getValueType() { 194*a1a3b679SAndreas Boehler 195*a1a3b679SAndreas Boehler return "TEXT"; 196*a1a3b679SAndreas Boehler 197*a1a3b679SAndreas Boehler } 198*a1a3b679SAndreas Boehler 199*a1a3b679SAndreas Boehler /** 200*a1a3b679SAndreas Boehler * Turns the object back into a serialized blob. 201*a1a3b679SAndreas Boehler * 202*a1a3b679SAndreas Boehler * @return string 203*a1a3b679SAndreas Boehler */ 204*a1a3b679SAndreas Boehler public function serialize() { 205*a1a3b679SAndreas Boehler 206*a1a3b679SAndreas Boehler // We need to kick in a special type of encoding, if it's a 2.1 vcard. 207*a1a3b679SAndreas Boehler if ($this->root->getDocumentType() !== Document::VCARD21) { 208*a1a3b679SAndreas Boehler return parent::serialize(); 209*a1a3b679SAndreas Boehler } 210*a1a3b679SAndreas Boehler 211*a1a3b679SAndreas Boehler $val = $this->getParts(); 212*a1a3b679SAndreas Boehler 213*a1a3b679SAndreas Boehler if (isset($this->minimumPropertyValues[$this->name])) { 214*a1a3b679SAndreas Boehler $val = array_pad($val, $this->minimumPropertyValues[$this->name], ''); 215*a1a3b679SAndreas Boehler } 216*a1a3b679SAndreas Boehler 217*a1a3b679SAndreas Boehler // Imploding multiple parts into a single value, and splitting the 218*a1a3b679SAndreas Boehler // values with ;. 219*a1a3b679SAndreas Boehler if (count($val)>1) { 220*a1a3b679SAndreas Boehler foreach($val as $k=>$v) { 221*a1a3b679SAndreas Boehler $val[$k] = str_replace(';','\;', $v); 222*a1a3b679SAndreas Boehler } 223*a1a3b679SAndreas Boehler $val = implode(';', $val); 224*a1a3b679SAndreas Boehler } else { 225*a1a3b679SAndreas Boehler $val = $val[0]; 226*a1a3b679SAndreas Boehler } 227*a1a3b679SAndreas Boehler 228*a1a3b679SAndreas Boehler $str = $this->name; 229*a1a3b679SAndreas Boehler if ($this->group) $str = $this->group . '.' . $this->name; 230*a1a3b679SAndreas Boehler foreach($this->parameters as $param) { 231*a1a3b679SAndreas Boehler 232*a1a3b679SAndreas Boehler if ($param->getValue() === 'QUOTED-PRINTABLE') { 233*a1a3b679SAndreas Boehler continue; 234*a1a3b679SAndreas Boehler } 235*a1a3b679SAndreas Boehler $str.=';' . $param->serialize(); 236*a1a3b679SAndreas Boehler 237*a1a3b679SAndreas Boehler } 238*a1a3b679SAndreas Boehler 239*a1a3b679SAndreas Boehler 240*a1a3b679SAndreas Boehler 241*a1a3b679SAndreas Boehler // If the resulting value contains a \n, we must encode it as 242*a1a3b679SAndreas Boehler // quoted-printable. 243*a1a3b679SAndreas Boehler if (strpos($val,"\n") !== false) { 244*a1a3b679SAndreas Boehler 245*a1a3b679SAndreas Boehler $str.=';ENCODING=QUOTED-PRINTABLE:'; 246*a1a3b679SAndreas Boehler $lastLine=$str; 247*a1a3b679SAndreas Boehler $out = null; 248*a1a3b679SAndreas Boehler 249*a1a3b679SAndreas Boehler // The PHP built-in quoted-printable-encode does not correctly 250*a1a3b679SAndreas Boehler // encode newlines for us. Specifically, the \r\n sequence must in 251*a1a3b679SAndreas Boehler // vcards be encoded as =0D=OA and we must insert soft-newlines 252*a1a3b679SAndreas Boehler // every 75 bytes. 253*a1a3b679SAndreas Boehler for($ii=0;$ii<strlen($val);$ii++) { 254*a1a3b679SAndreas Boehler $ord = ord($val[$ii]); 255*a1a3b679SAndreas Boehler // These characters are encoded as themselves. 256*a1a3b679SAndreas Boehler if ($ord >= 32 && $ord <=126) { 257*a1a3b679SAndreas Boehler $lastLine.=$val[$ii]; 258*a1a3b679SAndreas Boehler } else { 259*a1a3b679SAndreas Boehler $lastLine.='=' . strtoupper(bin2hex($val[$ii])); 260*a1a3b679SAndreas Boehler } 261*a1a3b679SAndreas Boehler if (strlen($lastLine)>=75) { 262*a1a3b679SAndreas Boehler // Soft line break 263*a1a3b679SAndreas Boehler $out.=$lastLine. "=\r\n "; 264*a1a3b679SAndreas Boehler $lastLine = null; 265*a1a3b679SAndreas Boehler } 266*a1a3b679SAndreas Boehler 267*a1a3b679SAndreas Boehler } 268*a1a3b679SAndreas Boehler if (!is_null($lastLine)) $out.= $lastLine . "\r\n"; 269*a1a3b679SAndreas Boehler return $out; 270*a1a3b679SAndreas Boehler 271*a1a3b679SAndreas Boehler } else { 272*a1a3b679SAndreas Boehler $str.=':' . $val; 273*a1a3b679SAndreas Boehler $out = ''; 274*a1a3b679SAndreas Boehler while(strlen($str)>0) { 275*a1a3b679SAndreas Boehler if (strlen($str)>75) { 276*a1a3b679SAndreas Boehler $out.= mb_strcut($str,0,75,'utf-8') . "\r\n"; 277*a1a3b679SAndreas Boehler $str = ' ' . mb_strcut($str,75,strlen($str),'utf-8'); 278*a1a3b679SAndreas Boehler } else { 279*a1a3b679SAndreas Boehler $out.=$str . "\r\n"; 280*a1a3b679SAndreas Boehler $str=''; 281*a1a3b679SAndreas Boehler break; 282*a1a3b679SAndreas Boehler } 283*a1a3b679SAndreas Boehler } 284*a1a3b679SAndreas Boehler 285*a1a3b679SAndreas Boehler return $out; 286*a1a3b679SAndreas Boehler 287*a1a3b679SAndreas Boehler 288*a1a3b679SAndreas Boehler } 289*a1a3b679SAndreas Boehler 290*a1a3b679SAndreas Boehler } 291*a1a3b679SAndreas Boehler 292*a1a3b679SAndreas Boehler /** 293*a1a3b679SAndreas Boehler * Validates the node for correctness. 294*a1a3b679SAndreas Boehler * 295*a1a3b679SAndreas Boehler * The following options are supported: 296*a1a3b679SAndreas Boehler * - Node::REPAIR - If something is broken, and automatic repair may 297*a1a3b679SAndreas Boehler * be attempted. 298*a1a3b679SAndreas Boehler * 299*a1a3b679SAndreas Boehler * An array is returned with warnings. 300*a1a3b679SAndreas Boehler * 301*a1a3b679SAndreas Boehler * Every item in the array has the following properties: 302*a1a3b679SAndreas Boehler * * level - (number between 1 and 3 with severity information) 303*a1a3b679SAndreas Boehler * * message - (human readable message) 304*a1a3b679SAndreas Boehler * * node - (reference to the offending node) 305*a1a3b679SAndreas Boehler * 306*a1a3b679SAndreas Boehler * @param int $options 307*a1a3b679SAndreas Boehler * @return array 308*a1a3b679SAndreas Boehler */ 309*a1a3b679SAndreas Boehler public function validate($options = 0) { 310*a1a3b679SAndreas Boehler 311*a1a3b679SAndreas Boehler $warnings = parent::validate($options); 312*a1a3b679SAndreas Boehler 313*a1a3b679SAndreas Boehler if (isset($this->minimumPropertyValues[$this->name])) { 314*a1a3b679SAndreas Boehler 315*a1a3b679SAndreas Boehler $minimum = $this->minimumPropertyValues[$this->name]; 316*a1a3b679SAndreas Boehler $parts = $this->getParts(); 317*a1a3b679SAndreas Boehler if (count($parts) < $minimum) { 318*a1a3b679SAndreas Boehler $warnings[] = array( 319*a1a3b679SAndreas Boehler 'level' => 1, 320*a1a3b679SAndreas Boehler 'message' => 'This property must have at least ' . $minimum . ' components. It only has ' . count($parts), 321*a1a3b679SAndreas Boehler 'node' => $this, 322*a1a3b679SAndreas Boehler ); 323*a1a3b679SAndreas Boehler if ($options & self::REPAIR) { 324*a1a3b679SAndreas Boehler $parts = array_pad($parts, $minimum, ''); 325*a1a3b679SAndreas Boehler $this->setParts($parts); 326*a1a3b679SAndreas Boehler } 327*a1a3b679SAndreas Boehler } 328*a1a3b679SAndreas Boehler 329*a1a3b679SAndreas Boehler } 330*a1a3b679SAndreas Boehler return $warnings; 331*a1a3b679SAndreas Boehler 332*a1a3b679SAndreas Boehler } 333*a1a3b679SAndreas Boehler} 334