Completed
Pull Request — master (#105)
by Tom
02:40
created

VCardParser::parseLine()   C

Complexity

Conditions 9
Paths 3

Size

Total Lines 60
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 60
rs 6.8358
c 0
b 0
f 0
cc 9
eloc 34
nc 3
nop 2

How to fix   Long Method   

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 JeroenDesloovere\VCard;
4
5
/*
6
 * This file is part of the VCard PHP Class from Jeroen Desloovere.
7
 *
8
 * For the full copyright and license information, please view the license
9
 * file that was distributed with this source code.
10
 */
11
12
use JeroenDesloovere\VCard\Exception\InvalidVersionException;
13
use JeroenDesloovere\VCard\Model\VCard;
14
use JeroenDesloovere\VCard\Model\VCardAddress;
15
use JeroenDesloovere\VCard\Model\VCardMedia;
16
use JeroenDesloovere\VCard\Util\GeneralUtil;
17
18
/**
19
 * VCard PHP Class to parse .vcard files.
20
 *
21
 * This class is heavily based on the Zendvcard project (seemingly abandoned),
22
 * which is licensed under the Apache 2.0 license.
23
 * More information can be found at https://code.google.com/archive/p/zendvcard/
24
 */
25
class VCardParser
26
{
27
    /**
28
     * The raw VCard content.
29
     *
30
     * @var string
31
     */
32
    protected $content;
33
34
    /**
35
     * The raw VCard content.
36
     *
37
     * @var string[]
38
     */
39
    protected $cardsContent;
40
41
    /**
42
     * The VCard data objects.
43
     *
44
     * @var array
45
     */
46
    protected $vcardObjects;
47
48
    /**
49
     * VCardParser constructor.
50
     *
51
     * @param string $content
52
     *
53
     * @throws InvalidVersionException
54
     */
55
    public function __construct($content)
56
    {
57
        $this->content = $content;
58
        $this->vcardObjects = [];
59
        $this->parse();
60
    }
61
62
    /**
63
     * Helper function to parse a file directly.
64
     *
65
     * @param string $filename
66
     *
67
     * @return self
68
     * @throws \RuntimeException
69
     */
70
    public static function parseFromFile(string $filename): VCardParser
71
    {
72
        if (file_exists($filename) && is_readable($filename)) {
73
            return new self(file_get_contents($filename));
74
        }
75
76
        throw new \RuntimeException(sprintf("File %s is not readable, or doesn't exist.", $filename));
77
    }
78
79
    /**
80
     * Fetch all the imported VCards.
81
     *
82
     * @return VCard[]
83
     *    A list of VCard card data objects.
84
     */
85
    public function getCards(): array
86
    {
87
        return $this->vcardObjects;
88
    }
89
90
    /**
91
     * Fetch the imported VCard at the specified index.
92
     *
93
     * @throws \OutOfBoundsException
94
     *
95
     * @param int $i
96
     *
97
     * @return VCard
98
     *    The card data object.
99
     */
100
    public function getCardAtIndex(int $i): VCard
101
    {
102
        if (isset($this->vcardObjects[$i])) {
103
            return $this->vcardObjects[$i];
104
        }
105
106
        throw new \OutOfBoundsException();
107
    }
108
109
    /**
110
     * @param int $i
111
     *
112
     * @return bool
113
     */
114
    public function hasCardAtIndex(int $i): bool
115
    {
116
        return !empty($this->vcardObjects[$i]);
117
    }
118
119
    /**
120
     * Start the parsing process.
121
     *
122
     * This method will populate the data object.
123
     *
124
     * @throws InvalidVersionException
125
     */
126
    protected function parse(): void
127
    {
128
        // Normalize new lines.
129
        $this->content = str_replace(["\r\n", "\r"], "\n", $this->content);
130
131
        $this->content = trim($this->content);
132
133
        $this->content = substr($this->content, 12, -10);
134
135
        // RFC2425 5.8.1. Line delimiting and folding
136
        // Unfolding is accomplished by regarding CRLF immediately followed by
137
        // a white space character (namely HTAB ASCII decimal 9 or. SPACE ASCII
138
        // decimal 32) as equivalent to no characters at all (i.e., the CRLF
139
        // and single white space character are removed).
140
        $this->content = preg_replace("/\n(?:[ \t])/", '', $this->content);
141
142
        $this->cardsContent = preg_split('/\nEND:VCARD\s+BEGIN:VCARD\n/', $this->content);
143
144
        foreach ($this->cardsContent as $cardContent) {
145
            $this->parseCard($cardContent);
146
        }
147
    }
148
149
    /**
150
     * @param string $cardContent
151
     *
152
     * @throws InvalidVersionException
153
     */
154
    protected function parseCard(string $cardContent): void
155
    {
156
        $cardData = new VCard();
157
158
        $lines = explode("\n", $cardContent);
159
160
        // Parse the VCard, line by line.
161
        foreach ($lines as $line) {
162
            $cardData = $this->parseLine($cardData, $line);
163
        }
164
165
        $this->vcardObjects[] = $cardData;
166
    }
167
168
    /**
169
     * @param VCard  $cardData
170
     * @param string $line
171
     *
172
     * @return VCard
173
     * @throws InvalidVersionException
174
     */
175
    protected function parseLine(VCard $cardData, string $line): VCard
176
    {
177
        $line = trim($line);
178
179
        if (!empty($line)) {
180
            // Strip grouping information. We don't use the group names. We
181
            // simply use a list for entries that have multiple values.
182
            // As per RFC, group names are alphanumerical, and end with a
183
            // period (.).
184
            $line = preg_replace('/^\w+\./', '', $line);
185
186
            @list($type, $value) = explode(':', $line, 2);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
187
188
            $types = explode(';', $type);
189
            $element = strtoupper($types[0]);
190
191
            array_shift($types);
192
193
            // Normalize types. A type can either be a type-param directly,
194
            // or can be prefixed with "type=". E.g.: "INTERNET" or
195
            // "type=INTERNET".
196
            if (!empty($types)) {
197
                $types = array_map(
198
                    function ($type) {
199
                        return preg_replace('/^type=/i', '', $type);
200
                    },
201
                    $types
202
                );
203
            }
204
205
            $rawValue = false;
206
            foreach ($types as $i => $type) {
207
                if (false !== stripos($type, 'base64')) {
208
                    $value = base64_decode($value);
209
                    unset($types[$i]);
210
                    $rawValue = true;
211
                } elseif (preg_match('/encoding=b/i', $type)) {
212
                    $value = base64_decode($value);
213
                    unset($types[$i]);
214
                    $rawValue = true;
215
                } elseif (false !== stripos($type, 'quoted-printable')) {
216
                    $value = quoted_printable_decode($value);
217
                    unset($types[$i]);
218
                    $rawValue = true;
219
                } elseif (stripos($type, 'charset=') === 0) {
220
                    $encoding = substr($type, 8);
221
222
                    if (\in_array($encoding, mb_list_encodings(), true)) {
223
                        $value = mb_convert_encoding($value, 'UTF-8', $encoding);
224
                    }
225
226
                    unset($types[$i]);
227
                }
228
            }
229
230
            $cardData = $this->parseOutput($cardData, $element, $value, $types, $rawValue);
231
        }
232
233
        return $cardData;
234
    }
235
236
    /**
237
     * @param VCard  $cardData
238
     * @param string $element
239
     * @param string $value
240
     * @param array  $types
241
     * @param bool   $rawValue
242
     *
243
     * @return VCard
244
     * @throws InvalidVersionException
245
     */
246
    protected function parseOutput(VCard $cardData, string $element, string $value, array $types, bool $rawValue): VCard
247
    {
248
        switch (strtoupper($element)) {
249
            case 'FN':
250
                $cardData->setFullName($value);
251
                break;
252
            case 'N':
253
                @list(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
254
                    $lastname,
255
                    $firstname,
256
                    $additional,
257
                    $prefix,
258
                    $suffix
259
                    ) = explode(';', $value);
260
261
                $cardData->setLastName($lastname);
262
                $cardData->setFirstName($firstname);
263
                $cardData->setAdditional($additional);
264
                $cardData->setPrefix($prefix);
265
                $cardData->setSuffix($suffix);
266
                break;
267
            case 'BDAY':
268
                $cardData->setBirthday(new \DateTime($value));
269
                break;
270
            case 'ADR':
271
                $key = GeneralUtil::parseKey($types, 'WORK;POSTAL');
272
                $address = new VCardAddress();
273
                $address->parser('4.0', $key, $value);
274
                $cardData->addAddress($address, $key);
275
                break;
276
            case 'TEL':
277
                $key = GeneralUtil::parseKey($types);
278
                $cardData->addPhone($value, $key);
279
                break;
280
            case 'EMAIL':
281
                $key = GeneralUtil::parseKey($types);
282
                $cardData->addEmail($value, $key);
283
                break;
284
            case 'REV':
285
                $cardData->setRevision($value);
286
                break;
287
            case 'VERSION':
288
                $cardData->setVersion($value);
289
                break;
290
            case 'ORG':
291
                $cardData->setOrganization($value);
292
                break;
293
            case 'URL':
294
                $key = GeneralUtil::parseKey($types);
295
                $cardData->addUrl($value, $key);
296
                break;
297
            case 'TITLE':
298
                $cardData->setTitle($value);
299
                break;
300
            case 'PHOTO':
301
                $media = new VCardMedia();
302
                $media->parser($value, $rawValue);
303
                $cardData->setPhoto($media);
304
                break;
305
            case 'LOGO':
306
                $media = new VCardMedia();
307
                $media->parser($value, $rawValue);
308
                $cardData->setLogo($media);
309
                break;
310
            case 'NOTE':
311
                $cardData->setNote(GeneralUtil::unescape($value));
312
                break;
313
            case 'CATEGORIES':
314
                $cardData->setCategories(array_map('trim', explode(',', $value)));
315
                break;
316
        }
317
318
        return $cardData;
319
    }
320
}
321