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

PropertyService::addName()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 41
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 41
rs 8.8571
c 0
b 0
f 0
cc 3
eloc 25
nc 3
nop 5
1
<?php
2
3
namespace JeroenDesloovere\VCard\Service;
4
5
use Behat\Transliterator\Transliterator;
6
use JeroenDesloovere\VCard\Exception\ElementAlreadyExistsException;
7
use JeroenDesloovere\VCard\Model\VCard;
8
use JeroenDesloovere\VCard\Model\VCardAddress;
9
use JeroenDesloovere\VCard\Model\VCardMedia;
10
11
/**
12
 * Class PropertyService
13
 *
14
 * @package JeroenDesloovere\VCard\Util
15
 */
16
class PropertyService
17
{
18
    /**
19
     * definedElements
20
     *
21
     * @var array
22
     */
23
    private $definedElements;
24
25
    /**
26
     * FileName
27
     *
28
     * @var string|null
29
     */
30
    private $fileName;
31
32
    /**
33
     * Multiple properties for element allowed
34
     *
35
     * @var array
36
     */
37
    private static $multiplePropertiesForElementAllowed = [
38
        'email',
39
        'address',
40
        'phoneNumber',
41
        'url',
42
    ];
43
44
    /**
45
     * Properties
46
     *
47
     * @var array
48
     */
49
    private $properties = [];
50
51
    /**
52
     * Default Charset
53
     *
54
     * @var string
55
     */
56
    private $charset;
57
58
    /**
59
     * @var VCard[]
60
     */
61
    private $vCards;
62
63
    /**
64
     * PropertyService constructor.
65
     *
66
     * @param VCard|VCard[] $vCard
67
     * @param string        $charset
68
     *
69
     * @throws ElementAlreadyExistsException
70
     */
71
    public function __construct($vCard, $charset = 'utf-8')
72
    {
73
        $this->vCards = $vCard;
0 ignored issues
show
Documentation Bug introduced by
It seems like $vCard can also be of type object<JeroenDesloovere\VCard\Model\VCard>. However, the property $vCards is declared as type array<integer,object<Jer...ere\VCard\Model\VCard>>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
74
        if (!\is_array($vCard)) {
75
            $this->vCards = [$vCard];
76
        }
77
78
        $this->charset = $charset;
79
80
        $this->parseVCarts();
81
    }
82
83
    /**
84
     * Get filename
85
     *
86
     * @return string|null
87
     */
88
    public function getFileName(): ?string
89
    {
90
        return $this->fileName;
91
    }
92
93
    /**
94
     * Get properties
95
     *
96
     * @return array
97
     */
98
    public function getProperties(): array
99
    {
100
        return $this->properties;
101
    }
102
103
    /**
104
     * Get charset string
105
     *
106
     * @return string
107
     */
108
    private function getCharsetString(): string
109
    {
110
        $charsetString = '';
111
112
        if ($this->charset === 'utf-8') {
113
            $charsetString = ';CHARSET=UTF-8';
114
        }
115
116
        return $charsetString;
117
    }
118
119
    /**
120
     * Set filename
121
     *
122
     * @param string|array $value
123
     * @param bool         $overwrite [optional] Default overwrite is true
124
     * @param string       $separator [optional] Default separator is an underscore '_'
125
     * @return void
126
     */
127
    private function setFileName($value, $overwrite = true, $separator = '_'): void
128
    {
129
        // recast to string if $value is array
130
        if (\is_array($value)) {
131
            $value = implode($separator, $value);
132
        }
133
134
        // trim unneeded values
135
        $value = trim($value, $separator);
136
137
        // remove all spaces
138
        $value = preg_replace('/\s+/', $separator, $value);
139
140
        // if value is empty, stop here
141
        if (empty($value)) {
142
            return;
143
        }
144
145
        // decode value
146
        $value = Transliterator::transliterate($value);
147
148
        // lowercase the string
149
        $value = strtolower($value);
150
151
        // urlize this part
152
        $value = Transliterator::urlize($value);
153
154
        // overwrite filename or add to filename using a prefix in between
155
        $this->fileName = $overwrite ?
156
            $value : $this->fileName.$separator.$value;
157
    }
158
159
    /**
160
     * Has property
161
     *
162
     * @param string $key
163
     * @return bool
164
     */
165
    private function hasProperty(string $key): bool
166
    {
167
        $properties = $this->getProperties();
168
169
        foreach ($properties as $property) {
170
            if ($property['key'] === $key && $property['value'] !== '') {
171
                return true;
172
            }
173
        }
174
175
        return false;
176
    }
177
178
    /**
179
     * @throws ElementAlreadyExistsException
180
     */
181
    private function parseVCarts(): void
182
    {
183
        foreach ($this->vCards as $vCard) {
184
            $this->parseVCart($vCard);
185
        }
186
    }
187
188
    /**
189
     * @param VCard $vCard
190
     *
191
     * @throws ElementAlreadyExistsException
192
     */
193
    private function parseVCart(VCard $vCard): void
194
    {
195
        $this->addAddress($vCard->getAddresses());
196
        $this->addBirthday($vCard->getBirthday());
197
        $this->addOrganization($vCard->getOrganization());
198
        $this->setArrayProperty('email', 'EMAIL;INTERNET', $vCard->getEmails());
199
        $this->setStringProperty('title', 'TITLE', $vCard->getTitle());
200
        $this->setStringProperty('role', 'ROLE', null); // TODO add Role to \JeroenDesloovere\VCard\Model\VCard
201
        $this->addName($vCard->getLastName(), $vCard->getFirstName(), $vCard->getAdditional(), $vCard->getPrefix(), $vCard->getSuffix());
202
        $this->setStringProperty('note', 'NOTE', $vCard->getNote());
203
        $this->addCategories($vCard->getCategories());
204
        $this->setArrayProperty('phoneNumber', 'TEL', $vCard->getPhones());
205
        $this->setMedia('logo', 'LOGO', $vCard->getLogo());
206
        $this->setMedia('photo', 'PHOTO', $vCard->getPhoto());
207
        $this->setArrayProperty('url', 'URL', $vCard->getUrls());
208
    }
209
210
    /**
211
     * @param VCardAddress[][]|null $addresses
212
     *
213
     * @throws ElementAlreadyExistsException
214
     */
215
    private function addAddress($addresses): void
216
    {
217
        if ($addresses !== null) {
218
            foreach ($addresses as $type => $sub) {
219
                foreach ($sub as $address) {
220
                    $this->setProperty(
221
                        'address',
222
                        'ADR'.(($type !== '') ? ';'.$type : '').$this->getCharsetString(),
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of $type (integer) and '' (string) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
223
                        $address->getAddress()
224
                    );
225
                }
226
            }
227
        }
228
    }
229
230
    /**
231
     * Add birthday
232
     *
233
     * @param \DateTime|null $date Format is YYYY-MM-DD
234
     *
235
     * @throws ElementAlreadyExistsException
236
     */
237
    private function addBirthday(?\DateTime $date): void
238
    {
239
        if ($date !== null) {
240
            $this->setProperty(
241
                'birthday',
242
                'BDAY'.$this->getCharsetString(),
243
                $date->format('Y-m-d')
244
            );
245
        }
246
    }
247
248
    /**
249
     * Add company
250
     *
251
     * @param null|string $company
252
     * @param string      $department
253
     *
254
     * @throws ElementAlreadyExistsException
255
     */
256
    private function addOrganization(?string $company, string $department = ''): void
257
    {
258
        if ($company !== null) {
259
            $this->setProperty(
260
                'organization',
261
                'ORG'.$this->getCharsetString(),
262
                $company.($department !== '' ? ';'.$department : '')
263
            );
264
265
            // if filename is empty, add to filename
266
            if ($this->fileName === null) {
267
                $this->setFileName($company);
268
            }
269
        }
270
    }
271
272
    /**
273
     * Add name
274
     *
275
     * @param string $lastName   [optional]
276
     * @param string $firstName  [optional]
277
     * @param string $additional [optional]
278
     * @param string $prefix     [optional]
279
     * @param string $suffix     [optional]
280
     *
281
     * @throws ElementAlreadyExistsException
282
     */
283
    private function addName(
284
        ?string $lastName = '',
285
        ?string $firstName = '',
286
        ?string $additional = '',
287
        ?string $prefix = '',
288
        ?string $suffix = ''
289
    ): void {
290
        if ($lastName !== null) {
291
            // define values with non-empty values
292
            $values = array_filter(
293
                [
294
                    $prefix,
295
                    $firstName,
296
                    $additional,
297
                    $lastName,
298
                    $suffix,
299
                ]
300
            );
301
302
            // define filename
303
            $this->setFileName($values);
304
305
            // set property
306
            $property = $lastName.';'.$firstName.';'.$additional.';'.$prefix.';'.$suffix;
307
            $this->setProperty(
308
                'name',
309
                'N'.$this->getCharsetString(),
310
                $property
311
            );
312
313
            // is property FN set?
314
            if (!$this->hasProperty('FN'.$this->getCharsetString())) {
315
                // set property
316
                $this->setProperty(
317
                    'fullname',
318
                    'FN'.$this->getCharsetString(),
319
                    trim(implode(' ', $values))
320
                );
321
            }
322
        }
323
    }
324
325
    /**
326
     * Add categories
327
     *
328
     * @param null|array $categories
329
     *
330
     * @throws ElementAlreadyExistsException
331
     */
332
    private function addCategories(?array $categories): void
333
    {
334
        if ($categories !== null) {
335
            $this->setProperty(
336
                'categories',
337
                'CATEGORIES'.$this->getCharsetString(),
338
                trim(implode(',', $categories))
339
            );
340
        }
341
    }
342
343
    /**
344
     * Add Array
345
     *
346
     * @param string          $element
347
     * @param string          $property
348
     * @param null|string[][] $values
349
     *
350
     * @throws ElementAlreadyExistsException
351
     */
352
    private function setArrayProperty(string $element, string $property, $values): void
353
    {
354
        if ($values !== null) {
355
            foreach ($values as $type => $sub) {
356
                foreach ($sub as $url) {
357
                    $this->setProperty(
358
                        $element,
359
                        $property.(($type !== '') ? ';'.$type : '').$this->getCharsetString(),
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of $type (integer) and '' (string) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
360
                        $url
361
                    );
362
                }
363
            }
364
        }
365
    }
366
367
    /**
368
     * Set string property
369
     *
370
     * @param string      $element
371
     * @param string      $property
372
     * @param null|string $value
373
     *
374
     * @throws ElementAlreadyExistsException
375
     */
376
    private function setStringProperty(string $element, string $property, ?string $value): void
377
    {
378
        if ($value !== null) {
379
            $this->setProperty(
380
                $element,
381
                $property.$this->getCharsetString(),
382
                $value
383
            );
384
        }
385
    }
386
387
    /**
388
     * Set Media
389
     *
390
     * @param string          $element
391
     * @param string          $property
392
     * @param VCardMedia|null $media
393
     *
394
     * @throws ElementAlreadyExistsException
395
     */
396
    private function setMedia(string $element, string $property, ?VCardMedia $media): void
397
    {
398
        if ($media !== null) {
399
            $result = [];
400
401
            if ($media->getUrl() !== null) {
402
                $result = $media->builderUrl($property);
403
            }
404
405
            if ($media->getRaw() !== null) {
406
                $result = $media->builderRaw($property);
407
            }
408
409
            if ($media->getUrl() !== null || $media->getRaw() !== null) {
410
                $this->setProperty(
411
                    $element,
412
                    $result['key'],
413
                    $result['value']
414
                );
415
            }
416
        }
417
    }
418
419
    /**
420
     * Set property
421
     *
422
     * @param string $element The element name you want to set, f.e.: name, email, phoneNumber, ...
423
     * @param string $key
424
     * @param string $value
425
     *
426
     * @throws ElementAlreadyExistsException
427
     */
428
    private function setProperty(string $element, string $key, string $value): void
429
    {
430
        if (isset($this->definedElements[$element])
431
            && !\in_array($element, $this::$multiplePropertiesForElementAllowed, true)) {
432
            throw new ElementAlreadyExistsException($element);
433
        }
434
435
        // we define that we set this element
436
        $this->definedElements[$element] = true;
437
438
        // adding property
439
        $this->properties[] = [
440
            'key' => $key,
441
            'value' => $value,
442
        ];
443
    }
444
}
445