Test Failed
Push — main ( c8394f...8477f1 )
by Rafael
66:21
created

VCardConverter::convertProperty()   F

Complexity

Conditions 42
Paths 1873

Size

Total Lines 176
Code Lines 96

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 42
eloc 96
nc 1873
nop 4
dl 0
loc 176
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Sabre\VObject;
4
5
/**
6
 * This utility converts vcards from one version to another.
7
 *
8
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
9
 * @author Evert Pot (http://evertpot.com/)
10
 * @license http://sabre.io/license/ Modified BSD License
11
 */
12
class VCardConverter
13
{
14
    /**
15
     * Converts a vCard object to a new version.
16
     *
17
     * targetVersion must be one of:
18
     *   Document::VCARD21
19
     *   Document::VCARD30
20
     *   Document::VCARD40
21
     *
22
     * Currently only 3.0 and 4.0 as input and output versions.
23
     *
24
     * 2.1 has some minor support for the input version, it's incomplete at the
25
     * moment though.
26
     *
27
     * If input and output version are identical, a clone is returned.
28
     *
29
     * @param int $targetVersion
30
     */
31
    public function convert(Component\VCard $input, $targetVersion)
32
    {
33
        $inputVersion = $input->getDocumentType();
34
        if ($inputVersion === $targetVersion) {
35
            return clone $input;
36
        }
37
38
        if (!in_array($inputVersion, [Document::VCARD21, Document::VCARD30, Document::VCARD40])) {
39
            throw new \InvalidArgumentException('Only vCard 2.1, 3.0 and 4.0 are supported for the input data');
40
        }
41
        if (!in_array($targetVersion, [Document::VCARD30, Document::VCARD40])) {
42
            throw new \InvalidArgumentException('You can only use vCard 3.0 or 4.0 for the target version');
43
        }
44
45
        $newVersion = Document::VCARD40 === $targetVersion ? '4.0' : '3.0';
46
47
        $output = new Component\VCard([
48
            'VERSION' => $newVersion,
49
        ]);
50
51
        // We might have generated a default UID. Remove it!
52
        unset($output->UID);
53
54
        foreach ($input->children() as $property) {
55
            $this->convertProperty($input, $output, $property, $targetVersion);
56
        }
57
58
        return $output;
59
    }
60
61
    /**
62
     * Handles conversion of a single property.
63
     *
64
     * @param int $targetVersion
65
     */
66
    protected function convertProperty(Component\VCard $input, Component\VCard $output, Property $property, $targetVersion)
67
    {
68
        // Skipping these, those are automatically added.
69
        if (in_array($property->name, ['VERSION', 'PRODID'])) {
70
            return;
71
        }
72
73
        $parameters = $property->parameters();
74
        $valueType = null;
75
        if (isset($parameters['VALUE'])) {
76
            $valueType = $parameters['VALUE']->getValue();
77
            unset($parameters['VALUE']);
78
        }
79
        if (!$valueType) {
80
            $valueType = $property->getValueType();
81
        }
82
        if (Document::VCARD30 !== $targetVersion && 'PHONE-NUMBER' === $valueType) {
83
            $valueType = null;
84
        }
85
        $newProperty = $output->createProperty(
86
            $property->name,
87
            $property->getParts(),
88
            [], // parameters will get added a bit later.
89
            $valueType
90
        );
91
92
        if (Document::VCARD30 === $targetVersion) {
93
            if ($property instanceof Property\Uri && in_array($property->name, ['PHOTO', 'LOGO', 'SOUND'])) {
94
                $newProperty = $this->convertUriToBinary($output, $newProperty);
95
            } elseif ($property instanceof Property\VCard\DateAndOrTime) {
96
                // In vCard 4, the birth year may be optional. This is not the
97
                // case for vCard 3. Apple has a workaround for this that
98
                // allows applications that support Apple's extension still
99
                // omit birthyears in vCard 3, but applications that do not
100
                // support this, will just use a random birthyear. We're
101
                // choosing 1604 for the birthyear, because that's what apple
102
                // uses.
103
                $parts = DateTimeParser::parseVCardDateTime($property->getValue());
104
                if (is_null($parts['year'])) {
105
                    $newValue = '1604-' . $parts['month'] . '-' . $parts['date'];
106
                    $newProperty->setValue($newValue);
107
                    $newProperty['X-APPLE-OMIT-YEAR'] = '1604';
108
                }
109
110
                if ('ANNIVERSARY' == $newProperty->name) {
111
                    // Microsoft non-standard anniversary
112
                    $newProperty->name = 'X-ANNIVERSARY';
113
114
                    // We also need to add a new apple property for the same
115
                    // purpose. This apple property needs a 'label' in the same
116
                    // group, so we first need to find a groupname that doesn't
117
                    // exist yet.
118
                    $x = 1;
119
                    while ($output->select('ITEM' . $x . '.')) {
120
                        ++$x;
121
                    }
122
                    $output->add('ITEM' . $x . '.X-ABDATE', $newProperty->getValue(), ['VALUE' => 'DATE-AND-OR-TIME']);
123
                    $output->add('ITEM' . $x . '.X-ABLABEL', '_$!<Anniversary>!$_');
124
                }
125
            } elseif ('KIND' === $property->name) {
126
                switch (strtolower($property->getValue())) {
127
                    case 'org':
128
                        // vCard 3.0 does not have an equivalent to KIND:ORG,
129
                        // but apple has an extension that means the same
130
                        // thing.
131
                        $newProperty = $output->createProperty('X-ABSHOWAS', 'COMPANY');
132
                        break;
133
134
                    case 'individual':
135
                        // Individual is implicit, so we skip it.
136
                        return;
137
138
                    case 'group':
139
                        // OS X addressbook property
140
                        $newProperty = $output->createProperty('X-ADDRESSBOOKSERVER-KIND', 'GROUP');
141
                        break;
142
                }
143
            } elseif ('MEMBER' === $property->name) {
144
                $newProperty = $output->createProperty('X-ADDRESSBOOKSERVER-MEMBER', $property->getValue());
145
            }
146
        } elseif (Document::VCARD40 === $targetVersion) {
147
            // These properties were removed in vCard 4.0
148
            if (in_array($property->name, ['NAME', 'MAILER', 'LABEL', 'CLASS'])) {
149
                return;
150
            }
151
152
            if ($property instanceof Property\Binary) {
153
                $newProperty = $this->convertBinaryToUri($output, $newProperty, $parameters);
154
            } elseif ($property instanceof Property\VCard\DateAndOrTime && isset($parameters['X-APPLE-OMIT-YEAR'])) {
155
                // If a property such as BDAY contained 'X-APPLE-OMIT-YEAR',
156
                // then we're stripping the year from the vcard 4 value.
157
                $parts = DateTimeParser::parseVCardDateTime($property->getValue());
158
                if ($parts['year'] === $property['X-APPLE-OMIT-YEAR']->getValue()) {
159
                    $newValue = '--' . $parts['month'] . '-' . $parts['date'];
160
                    $newProperty->setValue($newValue);
161
                }
162
163
                // Regardless if the year matched or not, we do need to strip
164
                // X-APPLE-OMIT-YEAR.
165
                unset($parameters['X-APPLE-OMIT-YEAR']);
166
            }
167
            switch ($property->name) {
168
                case 'X-ABSHOWAS':
169
                    if ('COMPANY' === strtoupper($property->getValue())) {
170
                        $newProperty = $output->createProperty('KIND', 'ORG');
171
                    }
172
                    break;
173
                case 'X-ADDRESSBOOKSERVER-KIND':
174
                    if ('GROUP' === strtoupper($property->getValue())) {
175
                        $newProperty = $output->createProperty('KIND', 'GROUP');
176
                    }
177
                    break;
178
                case 'X-ADDRESSBOOKSERVER-MEMBER':
179
                    $newProperty = $output->createProperty('MEMBER', $property->getValue());
180
                    break;
181
                case 'X-ANNIVERSARY':
182
                    $newProperty->name = 'ANNIVERSARY';
183
                    // If we already have an anniversary property with the same
184
                    // value, ignore.
185
                    foreach ($output->select('ANNIVERSARY') as $anniversary) {
186
                        if ($anniversary->getValue() === $newProperty->getValue()) {
187
                            return;
188
                        }
189
                    }
190
                    break;
191
                case 'X-ABDATE':
192
                    // Find out what the label was, if it exists.
193
                    if (!$property->group) {
194
                        break;
195
                    }
196
                    $label = $input->{$property->group . '.X-ABLABEL'};
197
198
                    // We only support converting anniversaries.
199
                    if (!$label || '_$!<Anniversary>!$_' !== $label->getValue()) {
200
                        break;
201
                    }
202
203
                    // If we already have an anniversary property with the same
204
                    // value, ignore.
205
                    foreach ($output->select('ANNIVERSARY') as $anniversary) {
206
                        if ($anniversary->getValue() === $newProperty->getValue()) {
207
                            return;
208
                        }
209
                    }
210
                    $newProperty->name = 'ANNIVERSARY';
211
                    break;
212
                // Apple's per-property label system.
213
                case 'X-ABLABEL':
214
                    if ('_$!<Anniversary>!$_' === $newProperty->getValue()) {
215
                        // We can safely remove these, as they are converted to
216
                        // ANNIVERSARY properties.
217
                        return;
218
                    }
219
                    break;
220
            }
221
        }
222
223
        // set property group
224
        $newProperty->group = $property->group;
225
226
        if (Document::VCARD40 === $targetVersion) {
227
            $this->convertParameters40($newProperty, $parameters);
228
        } else {
229
            $this->convertParameters30($newProperty, $parameters);
230
        }
231
232
        // Lastly, we need to see if there's a need for a VALUE parameter.
233
        //
234
        // We can do that by instantiating a empty property with that name, and
235
        // seeing if the default valueType is identical to the current one.
236
        $tempProperty = $output->createProperty($newProperty->name);
237
        if ($tempProperty->getValueType() !== $newProperty->getValueType()) {
238
            $newProperty['VALUE'] = $newProperty->getValueType();
239
        }
240
241
        $output->add($newProperty);
242
    }
243
244
    /**
245
     * Converts a BINARY property to a URI property.
246
     *
247
     * vCard 4.0 no longer supports BINARY properties.
248
     *
249
     * @param Property\Uri $property the input property
250
     * @param $parameters list of parameters that will eventually be added to
251
     *                    the new property
252
     *
253
     * @return Property\Uri
254
     */
255
    protected function convertBinaryToUri(Component\VCard $output, Property\Binary $newProperty, array &$parameters)
256
    {
257
        $value = $newProperty->getValue();
258
        $newProperty = $output->createProperty(
259
            $newProperty->name,
260
            null, // no value
261
            [], // no parameters yet
262
            'URI' // Forcing the BINARY type
263
        );
264
265
        $mimeType = 'application/octet-stream';
266
267
        // See if we can find a better mimetype.
268
        if (isset($parameters['TYPE'])) {
269
            $newTypes = [];
270
            foreach ($parameters['TYPE']->getParts() as $typePart) {
271
                if (
272
                    in_array(
273
                    strtoupper($typePart),
274
                    ['JPEG', 'PNG', 'GIF']
275
                    )
276
                ) {
277
                    $mimeType = 'image/' . strtolower($typePart);
278
                } else {
279
                    $newTypes[] = $typePart;
280
                }
281
            }
282
283
            // If there were any parameters we're not converting to a
284
            // mime-type, we need to keep them.
285
            if ($newTypes) {
286
                $parameters['TYPE']->setParts($newTypes);
287
            } else {
288
                unset($parameters['TYPE']);
289
            }
290
        }
291
292
        $newProperty->setValue('data:' . $mimeType . ';base64,' . base64_encode($value));
293
294
        return $newProperty;
295
    }
296
297
    /**
298
     * Converts a URI property to a BINARY property.
299
     *
300
     * In vCard 4.0 attachments are encoded as data: uri. Even though these may
301
     * be valid in vCard 3.0 as well, we should convert those to BINARY if
302
     * possible, to improve compatibility.
303
     *
304
     * @param Property\Uri $property the input property
305
     *
306
     * @return Property\Binary|null
307
     */
308
    protected function convertUriToBinary(Component\VCard $output, Property\Uri $newProperty)
309
    {
310
        $value = $newProperty->getValue();
311
312
        // Only converting data: uris
313
        if ('data:' !== substr($value, 0, 5)) {
314
            return $newProperty;
315
        }
316
317
        $newProperty = $output->createProperty(
318
            $newProperty->name,
319
            null, // no value
320
            [], // no parameters yet
321
            'BINARY'
322
        );
323
324
        $mimeType = substr($value, 5, strpos($value, ',') - 5);
325
        if (strpos($mimeType, ';')) {
326
            $mimeType = substr($mimeType, 0, strpos($mimeType, ';'));
327
            $newProperty->setValue(base64_decode(substr($value, strpos($value, ',') + 1)));
328
        } else {
329
            $newProperty->setValue(substr($value, strpos($value, ',') + 1));
330
        }
331
        unset($value);
332
333
        $newProperty['ENCODING'] = 'b';
334
        switch ($mimeType) {
335
            case 'image/jpeg':
336
                $newProperty['TYPE'] = 'JPEG';
337
                break;
338
            case 'image/png':
339
                $newProperty['TYPE'] = 'PNG';
340
                break;
341
            case 'image/gif':
342
                $newProperty['TYPE'] = 'GIF';
343
                break;
344
        }
345
346
        return $newProperty;
347
    }
348
349
    /**
350
     * Adds parameters to a new property for vCard 4.0.
351
     */
352
    protected function convertParameters40(Property $newProperty, array $parameters)
353
    {
354
        // Adding all parameters.
355
        foreach ($parameters as $param) {
356
            // vCard 2.1 allowed parameters with no name
357
            if ($param->noName) {
358
                $param->noName = false;
359
            }
360
361
            switch ($param->name) {
362
                // We need to see if there's any TYPE=PREF, because in vCard 4
363
                // that's now PREF=1.
364
                case 'TYPE':
365
                    foreach ($param->getParts() as $paramPart) {
366
                        if ('PREF' === strtoupper($paramPart)) {
367
                            $newProperty->add('PREF', '1');
368
                        } else {
369
                            $newProperty->add($param->name, $paramPart);
370
                        }
371
                    }
372
                    break;
373
                // These no longer exist in vCard 4
374
                case 'ENCODING':
375
                case 'CHARSET':
376
                    break;
377
378
                default:
379
                    $newProperty->add($param->name, $param->getParts());
380
                    break;
381
            }
382
        }
383
    }
384
385
    /**
386
     * Adds parameters to a new property for vCard 3.0.
387
     */
388
    protected function convertParameters30(Property $newProperty, array $parameters)
389
    {
390
        // Adding all parameters.
391
        foreach ($parameters as $param) {
392
            // vCard 2.1 allowed parameters with no name
393
            if ($param->noName) {
394
                $param->noName = false;
395
            }
396
397
            switch ($param->name) {
398
                case 'ENCODING':
399
                    // This value only existed in vCard 2.1, and should be
400
                    // removed for anything else.
401
                    if ('QUOTED-PRINTABLE' !== strtoupper($param->getValue())) {
402
                        $newProperty->add($param->name, $param->getParts());
403
                    }
404
                    break;
405
406
                /*
407
                 * Converting PREF=1 to TYPE=PREF.
408
                 *
409
                 * Any other PREF numbers we'll drop.
410
                 */
411
                case 'PREF':
412
                    if ('1' == $param->getValue()) {
413
                        $newProperty->add('TYPE', 'PREF');
414
                    }
415
                    break;
416
417
                default:
418
                    $newProperty->add($param->name, $param->getParts());
419
                    break;
420
            }
421
        }
422
    }
423
}
424