Completed
Push — master ( 80ae61...4726ae )
by Jeroen
8s
created

VCardParser::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 6
rs 9.4285
cc 1
eloc 4
nc 1
nop 1
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
/**
25
 * VCard PHP Class to parse .vcard files.
26
 *
27
 * This class is heavily based on the Zendvcard project (seemingly abandoned),
28
 * which is licensed under the Apache 2.0 license.
29
 * More information can be found at https://code.google.com/archive/p/zendvcard/
30
 *
31
 * @author Thomas Schaaf <[email protected]>
32
 * @author ruzicka.jan
33
 * @author Wouter Admiraal <[email protected]>
34
 */
35
class VCardParser
36
{
37
38
    /**
39
     * The raw VCard content.
40
    *
41
     * @var string
42
     */
43
    protected $content;
44
45
    /**
46
     * The VCard data objects.
47
     *
48
     * @var array
49
     */
50
    protected $vcardObjects;
51
52
    /**
53
     * Helper function to parse a file directly.
54
     *
55
     * @param string $filename
56
     *
57
     * @return JeroenDesloovere\VCard\VCardParser
58
     */
59
    public static function parseFromFile($filename)
60
    {
61
        if (file_exists($filename) && is_readable($filename)) {
62
            return new VCardParser(file_get_contents($filename));
63
        } else {
64
            throw new \RuntimeException(sprintf("File %s is not readable, or doesn't exist.", $filename));
65
        }
66
    }
67
68
    public function __construct($content)
69
    {
70
        $this->content = $content;
71
        $this->vcardObjects = array();
72
        $this->parse();
73
    }
74
75
    /**
76
     * Fetch the imported VCard at the specified index.
77
     *
78
     * @throws OutOfBoundsException
79
     *
80
     * @param int $i
81
     *
82
     * @return stdClass
83
     *    The card data object.
84
     */
85
    public function getCardAtIndex($i)
86
    {
87
        if (isset($this->vcardObjects[$i])) {
88
            return $this->vcardObjects[$i];
89
        }
90
        throw new \OutOfBoundsException();
91
    }
92
93
    /**
94
     * Start the parsing process.
95
     *
96
     * This method will populate the data object.
97
     */
98
    protected function parse()
99
    {
100
        // Normalize new lines.
101
        $this->content = str_replace(array("\r\n", "\r"), "\n", $this->content);
102
103
        // RFC2425 5.8.1. Line delimiting and folding
104
        // Unfolding is accomplished by regarding CRLF immediately followed by
105
        // a white space character (namely HTAB ASCII decimal 9 or. SPACE ASCII
106
        // decimal 32) as equivalent to no characters at all (i.e., the CRLF
107
        // and single white space character are removed).
108
        $this->content = preg_replace("/\n(?:[ \t])/", "", $this->content);
109
        $lines = explode("\n", $this->content);
110
111
        // Parse the VCard, line by line.
112
        foreach ($lines as $line) {
113
            $line = trim($line);
114
115
            if (strtoupper($line) == "BEGIN:VCARD") {
116
                $cardData = new \stdClass();
117
            } elseif (strtoupper($line) == "END:VCARD") {
118
                $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...
119
            } elseif (!empty($line)) {
120
                $type = '';
121
                $value = '';
122
                @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...
123
124
                $types = explode(';', $type);
125
                $element = strtoupper($types[0]);
126
127
                array_shift($types);
128
                $i = 0;
129
                $rawValue = false;
130
                foreach ($types as $type) {
131
                    if (preg_match('/base64/', strtolower($type))) {
132
                        $value = base64_decode($value);
133
                        unset($types[$i]);
134
                        $rawValue = true;
135
                    } elseif (preg_match('/encoding=b/', strtolower($type))) {
136
                        $value = base64_decode($value);
137
                        unset($types[$i]);
138
                        $rawValue = true;
139
                    } elseif (preg_match('/quoted-printable/', strtolower($type))) {
140
                        $value = quoted_printable_decode($value);
141
                        unset($types[$i]);
142
                        $rawValue = true;
143
                    } elseif (strpos(strtolower($type), 'charset=') === 0) {
144
                        try {
145
                            $value = mb_convert_encoding($value, "UTF-8", substr($type, 8));
146
                        } catch (\Exception $e) { }
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
147
                        unset($types[$i]);
148
                    }
149
                    $i++;
150
                }
151
152
                switch (strtoupper($element)) {
153
                    case 'FN':
154
                        $cardData->fullname = $value;
155
                        break;
156
                    case 'N':
157
                        foreach($this->parseName($value) as $key => $val) {
158
                            $cardData->{$key} = $val;
159
                        }
160
                        break;
161
                    case 'BDAY':
162
                        $cardData->birthday = $this->parseBirthday($value);
163
                        break;
164
                    case 'ADR':
165
                        if (!isset($cardData->address)) {
166
                            $cardData->address = array();
167
                        }
168
                        $key = !empty($types) ? implode(';', $types) : 'WORK;POSTAL';
169
                        $cardData->address[$key][] = $this->parseAddress($value);
170
                        break;
171 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...
172
                        if (!isset($cardData->phone)) {
173
                            $cardData->phone = array();
174
                        }
175
                        $key = !empty($types) ? implode(';', $types) : 'default';
176
                        $cardData->phone[$key][] = $value;
177
                        break;
178 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...
179
                        if (!isset($cardData->email)) {
180
                            $cardData->email = array();
181
                        }
182
                        $key = !empty($types) ? implode(';', $types) : 'default';
183
                        $cardData->email[$key][] = $value;
184
                        break;
185
                    case 'REV':
186
                        $cardData->revision = $value;
187
                        break;
188
                    case 'VERSION':
189
                        $cardData->version = $value;
190
                        break;
191
                    case 'ORG':
192
                        $cardData->organization = $value;
193
                        break;
194 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...
195
                        if (!isset($cardData->url)) {
196
                            $cardData->url = array();
197
                        }
198
                        $key = !empty($types) ? implode(';', $types) : 'default';
199
                        $cardData->url[$key][] = $value;
200
                        break;
201
                    case 'TITLE':
202
                        $cardData->title = $value;
203
                        break;
204
                    case 'PHOTO':
205
                        if ($rawValue) {
206
                            $cardData->rawPhoto = $value;
207
                        } else {
208
                            $cardData->photo = $value;
209
                        }
210
                        break;
211
                }
212
            }
213
        }
214
    }
215
216
    protected function parseName($value)
217
    {
218
        @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...
219
            $lastname,
220
            $firstname,
221
            $additional,
222
            $prefix,
223
            $suffix
224
        ) = explode(';', $value);
225
        return (object) array(
226
            'lastname' => $lastname,
227
            'firstname' => $firstname,
228
            'additional' => $additional,
229
            'prefix' => $prefix,
230
            'suffix' => $suffix,
231
        );
232
    }
233
234
    protected function parseBirthday($value)
235
    {
236
        return new \DateTime($value);
237
    }
238
239
    protected function parseAddress($value)
240
    {
241
        @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...
242
            $name,
243
            $extended,
244
            $street,
245
            $city,
246
            $region,
247
            $zip,
248
            $country,
249
        ) = explode(';', $value);
250
        return (object) array(
251
            'name' => $name,
252
            'extended' => $extended,
253
            'street' => $street,
254
            'city' => $city,
255
            'region' => $region,
256
            'zip' => $zip,
257
            'country' => $country,
258
        );
259
    }
260
261
}
262