Completed
Push — master ( 4c43f2...15d11c )
by Jeroen
8s
created

VCardParser::rewind()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace JeroenDesloovere\VCard;
4
5
/*
6
 * Copyright 2010 Thomas Schaaf <[email protected]>
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 *     http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 *
20
 * Changes by: Wouter Admiraal <[email protected]>
21
 * Original code is available at: http://code.google.com/p/zendvcard/
22
 */
23
24
use Iterator;
25
26
/**
27
 * VCard PHP Class to parse .vcard files.
28
 *
29
 * This class is heavily based on the Zendvcard project (seemingly abandoned),
30
 * which is licensed under the Apache 2.0 license.
31
 * More information can be found at https://code.google.com/archive/p/zendvcard/
32
 *
33
 * @author Thomas Schaaf <[email protected]>
34
 * @author ruzicka.jan
35
 * @author Wouter Admiraal <[email protected]>
36
 */
37
class VCardParser implements Iterator
38
{
39
    /**
40
     * The raw VCard content.
41
    *
42
     * @var string
43
     */
44
    protected $content;
45
46
    /**
47
     * The VCard data objects.
48
     *
49
     * @var array
50
     */
51
    protected $vcardObjects;
52
53
    /**
54
     * The iterator position.
55
     *
56
     * @var int
57
     */
58
    protected $position;
59
60
    /**
61
     * Helper function to parse a file directly.
62
     *
63
     * @param string $filename
64
     *
65
     * @return JeroenDesloovere\VCard\VCardParser
66
     */
67
    public static function parseFromFile($filename)
68
    {
69
        if (file_exists($filename) && is_readable($filename)) {
70
            return new VCardParser(file_get_contents($filename));
71
        } else {
72
            throw new \RuntimeException(sprintf("File %s is not readable, or doesn't exist.", $filename));
73
        }
74
    }
75
76
    public function __construct($content)
77
    {
78
        $this->content = $content;
79
        $this->vcardObjects = array();
80
        $this->rewind();
81
        $this->parse();
82
    }
83
84
    public function rewind()
85
    {
86
        $this->position = 0;
87
    }
88
89
    public function current()
90
    {
91
        if ($this->valid()) {
92
            return $this->getCardAtIndex($this->position);
93
        }
94
    }
95
96
    public function key()
97
    {
98
        return $this->position;
99
    }
100
101
    public function next()
102
    {
103
        $this->position++;
104
    }
105
106
    public function valid()
107
    {
108
        return !empty($this->vcardObjects[$this->position]);
109
    }
110
111
    /**
112
     * Fetch all the imported VCards.
113
     *
114
     * @return array
115
     *    A list of VCard card data objects.
116
     */
117
    public function getCards()
118
    {
119
        return $this->vcardObjects;
120
    }
121
122
    /**
123
     * Fetch the imported VCard at the specified index.
124
     *
125
     * @throws OutOfBoundsException
126
     *
127
     * @param int $i
128
     *
129
     * @return stdClass
130
     *    The card data object.
131
     */
132
    public function getCardAtIndex($i)
133
    {
134
        if (isset($this->vcardObjects[$i])) {
135
            return $this->vcardObjects[$i];
136
        }
137
        throw new \OutOfBoundsException();
138
    }
139
140
    /**
141
     * Start the parsing process.
142
     *
143
     * This method will populate the data object.
144
     */
145
    protected function parse()
146
    {
147
        // Normalize new lines.
148
        $this->content = str_replace(array("\r\n", "\r"), "\n", $this->content);
149
150
        // RFC2425 5.8.1. Line delimiting and folding
151
        // Unfolding is accomplished by regarding CRLF immediately followed by
152
        // a white space character (namely HTAB ASCII decimal 9 or. SPACE ASCII
153
        // decimal 32) as equivalent to no characters at all (i.e., the CRLF
154
        // and single white space character are removed).
155
        $this->content = preg_replace("/\n(?:[ \t])/", "", $this->content);
156
        $lines = explode("\n", $this->content);
157
158
        // Parse the VCard, line by line.
159
        foreach ($lines as $line) {
160
            $line = trim($line);
161
162
            if (strtoupper($line) == "BEGIN:VCARD") {
163
                $cardData = new \stdClass();
164
            } elseif (strtoupper($line) == "END:VCARD") {
165
                $this->vcardObjects[] = $cardData;
0 ignored issues
show
Bug introduced by
The variable $cardData does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
166
            } elseif (!empty($line)) {
167
                $type = '';
168
                $value = '';
169
                @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...
170
171
                $types = explode(';', $type);
172
                $element = strtoupper($types[0]);
173
174
                array_shift($types);
175
                $i = 0;
176
                $rawValue = false;
177
                foreach ($types as $type) {
178
                    if (preg_match('/base64/', strtolower($type))) {
179
                        $value = base64_decode($value);
180
                        unset($types[$i]);
181
                        $rawValue = true;
182
                    } elseif (preg_match('/encoding=b/', strtolower($type))) {
183
                        $value = base64_decode($value);
184
                        unset($types[$i]);
185
                        $rawValue = true;
186
                    } elseif (preg_match('/quoted-printable/', strtolower($type))) {
187
                        $value = quoted_printable_decode($value);
188
                        unset($types[$i]);
189
                        $rawValue = true;
190
                    } elseif (strpos(strtolower($type), 'charset=') === 0) {
191
                        try {
192
                            $value = mb_convert_encoding($value, "UTF-8", substr($type, 8));
193
                        } catch (\Exception $e) { }
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
194
                        unset($types[$i]);
195
                    }
196
                    $i++;
197
                }
198
199
                switch (strtoupper($element)) {
200
                    case 'FN':
201
                        $cardData->fullname = $value;
202
                        break;
203
                    case 'N':
204
                        foreach($this->parseName($value) as $key => $val) {
205
                            $cardData->{$key} = $val;
206
                        }
207
                        break;
208
                    case 'BDAY':
209
                        $cardData->birthday = $this->parseBirthday($value);
210
                        break;
211
                    case 'ADR':
212
                        if (!isset($cardData->address)) {
213
                            $cardData->address = array();
214
                        }
215
                        $key = !empty($types) ? implode(';', $types) : 'WORK;POSTAL';
216
                        $cardData->address[$key][] = $this->parseAddress($value);
217
                        break;
218 View Code Duplication
                    case 'TEL':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
219
                        if (!isset($cardData->phone)) {
220
                            $cardData->phone = array();
221
                        }
222
                        $key = !empty($types) ? implode(';', $types) : 'default';
223
                        $cardData->phone[$key][] = $value;
224
                        break;
225 View Code Duplication
                    case 'EMAIL':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
226
                        if (!isset($cardData->email)) {
227
                            $cardData->email = array();
228
                        }
229
                        $key = !empty($types) ? implode(';', $types) : 'default';
230
                        $cardData->email[$key][] = $value;
231
                        break;
232
                    case 'REV':
233
                        $cardData->revision = $value;
234
                        break;
235
                    case 'VERSION':
236
                        $cardData->version = $value;
237
                        break;
238
                    case 'ORG':
239
                        $cardData->organization = $value;
240
                        break;
241 View Code Duplication
                    case 'URL':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
242
                        if (!isset($cardData->url)) {
243
                            $cardData->url = array();
244
                        }
245
                        $key = !empty($types) ? implode(';', $types) : 'default';
246
                        $cardData->url[$key][] = $value;
247
                        break;
248
                    case 'TITLE':
249
                        $cardData->title = $value;
250
                        break;
251
                    case 'PHOTO':
252
                        if ($rawValue) {
253
                            $cardData->rawPhoto = $value;
254
                        } else {
255
                            $cardData->photo = $value;
256
                        }
257
                        break;
258
                }
259
            }
260
        }
261
    }
262
263
    protected function parseName($value)
264
    {
265
        @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...
266
            $lastname,
267
            $firstname,
268
            $additional,
269
            $prefix,
270
            $suffix
271
        ) = explode(';', $value);
272
        return (object) array(
273
            'lastname' => $lastname,
274
            'firstname' => $firstname,
275
            'additional' => $additional,
276
            'prefix' => $prefix,
277
            'suffix' => $suffix,
278
        );
279
    }
280
281
    protected function parseBirthday($value)
282
    {
283
        return new \DateTime($value);
284
    }
285
286
    protected function parseAddress($value)
287
    {
288
        @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...
289
            $name,
290
            $extended,
291
            $street,
292
            $city,
293
            $region,
294
            $zip,
295
            $country,
296
        ) = explode(';', $value);
297
        return (object) array(
298
            'name' => $name,
299
            'extended' => $extended,
300
            'street' => $street,
301
            'city' => $city,
302
            'region' => $region,
303
            'zip' => $zip,
304
            'country' => $country,
305
        );
306
    }
307
308
}
309