1<?php
2
3namespace Sabre\VObject;
4
5use
6    ArrayObject;
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    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    /**
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     * @return string
83     */
84    public static function guessParameterNameByValue($value) {
85        switch(strtoupper($value)) {
86
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     * @return void
180     */
181    public function setValue($value) {
182
183        $this->value = $value;
184
185    }
186
187    /**
188     * Returns the current value
189     *
190     * This method will always return a string, or null. If there were multiple
191     * values, it will automatically concatinate them (separated by comma).
192     *
193     * @return string|null
194     */
195    public function getValue() {
196
197        if (is_array($this->value)) {
198            return implode(',' , $this->value);
199        } else {
200            return $this->value;
201        }
202
203    }
204
205    /**
206     * Sets multiple values for this parameter.
207     *
208     * @param array $value
209     * @return void
210     */
211    public function setParts(array $value) {
212
213        $this->value = $value;
214
215    }
216
217    /**
218     * Returns all values for this parameter.
219     *
220     * If there were no values, an empty array will be returned.
221     *
222     * @return array
223     */
224    public function getParts() {
225
226        if (is_array($this->value)) {
227            return $this->value;
228        } elseif (is_null($this->value)) {
229            return array();
230        } else {
231            return array($this->value);
232        }
233
234    }
235
236    /**
237     * Adds a value to this parameter
238     *
239     * If the argument is specified as an array, all items will be added to the
240     * parameter value list.
241     *
242     * @param string|array $part
243     * @return void
244     */
245    public function addValue($part) {
246
247        if (is_null($this->value)) {
248            $this->value = $part;
249        } else {
250            $this->value = array_merge((array)$this->value, (array)$part);
251        }
252
253    }
254
255    /**
256     * Checks if this parameter contains the specified value.
257     *
258     * This is a case-insensitive match. It makes sense to call this for for
259     * instance the TYPE parameter, to see if it contains a keyword such as
260     * 'WORK' or 'FAX'.
261     *
262     * @param string $value
263     * @return bool
264     */
265    public function has($value) {
266
267        return in_array(
268            strtolower($value),
269            array_map('strtolower', (array)$this->value)
270        );
271
272    }
273
274    /**
275     * Turns the object back into a serialized blob.
276     *
277     * @return string
278     */
279    public function serialize() {
280
281        $value = $this->getParts();
282
283        if (count($value)===0) {
284            return $this->name . '=';
285        }
286
287        if ($this->root->getDocumentType() === Document::VCARD21 && $this->noName) {
288
289            return implode(';', $value);
290
291        }
292
293        return $this->name . '=' . array_reduce(
294            $value,
295            function($out, $item) {
296
297                if (!is_null($out)) $out.=',';
298
299                // If there's no special characters in the string, we'll use the simple
300                // format.
301                //
302                // The list of special characters is defined as:
303                //
304                // Any character except CONTROL, DQUOTE, ";", ":", ","
305                //
306                // by the iCalendar spec:
307                // https://tools.ietf.org/html/rfc5545#section-3.1
308                //
309                // And we add ^ to that because of:
310                // https://tools.ietf.org/html/rfc6868
311                //
312                // But we've found that iCal (7.0, shipped with OSX 10.9)
313                // severaly trips on + characters not being quoted, so we
314                // added + as well.
315                if (!preg_match('#(?: [\n":;\^,\+] )#x', $item)) {
316                    return $out.$item;
317                } else {
318                    // Enclosing in double-quotes, and using RFC6868 for encoding any
319                    // special characters
320                    $out.='"' . strtr(
321                        $item,
322                        array(
323                            '^'  => '^^',
324                            "\n" => '^n',
325                            '"'  => '^\'',
326                        )
327                    ) . '"';
328                    return $out;
329                }
330
331            }
332        );
333
334    }
335
336    /**
337     * This method returns an array, with the representation as it should be
338     * encoded in json. This is used to create jCard or jCal documents.
339     *
340     * @return array
341     */
342    public function jsonSerialize() {
343
344        return $this->value;
345
346    }
347
348    /**
349     * Called when this object is being cast to a string
350     *
351     * @return string
352     */
353    public function __toString() {
354
355        return (string)$this->getValue();
356
357    }
358
359    /**
360     * Returns the iterator for this object
361     *
362     * @return ElementList
363     */
364    public function getIterator() {
365
366        if (!is_null($this->iterator))
367            return $this->iterator;
368
369        return $this->iterator = new ArrayObject((array)$this->value);
370
371    }
372
373}
374