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