Completed
Pull Request — master (#265)
by Luc
04:48
created

CdbXMLImporter   F

Complexity

Total Complexity 65

Size/Duplication

Total Lines 522
Duplicated Lines 4.41 %

Coupling/Cohesion

Components 1
Dependencies 26

Importance

Changes 0
Metric Value
wmc 65
lcom 1
cbo 26
dl 23
loc 522
rs 1.3043
c 0
b 0
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 31 1
B documentWithCdbXML() 13 82 4
B importDescription() 0 41 6
B importLocation() 0 26 3
D importOrganizer() 0 39 9
C importPriceInfo() 0 47 9
A importTerms() 10 20 4
B importTypicalAgeRange() 0 16 6
A importPerformers() 0 14 4
A importLanguages() 0 12 3
B importSeeAlso() 0 21 7
B importUitInVlaanderenReference() 0 27 4
A importAudience() 0 10 4
A longDescriptionStartsWithShortDescription() 0 8 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like CdbXMLImporter often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use CdbXMLImporter, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace CultuurNet\UDB3\Event\ReadModel\JSONLD;
4
5
use CultuurNet\UDB3\CalendarFactoryInterface;
6
use CultuurNet\UDB3\Cdb\CdbId\EventCdbIdExtractorInterface;
7
use CultuurNet\UDB3\Cdb\PriceDescriptionParser;
8
use CultuurNet\UDB3\Event\ValueObjects\Audience;
9
use CultuurNet\UDB3\Event\ValueObjects\AudienceType;
10
use CultuurNet\UDB3\LabelImporter;
11
use CultuurNet\UDB3\Offer\ReadModel\JSONLD\CdbXmlContactInfoImporterInterface;
12
use CultuurNet\UDB3\Offer\ReadModel\JSONLD\CdbXMLItemBaseImporter;
13
use CultuurNet\UDB3\SluggerInterface;
14
use CultuurNet\UDB3\StringFilter\BreakTagToNewlineStringFilter;
15
use CultuurNet\UDB3\StringFilter\CombinedStringFilter;
16
use CultuurNet\UDB3\StringFilter\ConsecutiveBlockOfTextStringFilter;
17
use CultuurNet\UDB3\StringFilter\StringFilterInterface;
18
use CultuurNet\UDB3\StringFilter\StripSourceStringFilter;
19
use CultuurNet\UDB3\StringFilter\StripSurroundingSpaceStringFilter;
20
21
/**
22
 * Takes care of importing cultural events in the CdbXML format (UDB2)
23
 * into a UDB3 JSON-LD document.
24
 */
25
class CdbXMLImporter
26
{
27
    /**
28
     * @var CdbXMLItemBaseImporter
29
     */
30
    private $cdbXMLItemBaseImporter;
31
32
    /**
33
     * @var EventCdbIdExtractorInterface
34
     */
35
    private $cdbIdExtractor;
36
37
    /**
38
     * @var PriceDescriptionParser
39
     */
40
    private $priceDescriptionParser;
41
42
    /**
43
     * @var StringFilterInterface
44
     */
45
    private $longDescriptionFilter;
46
47
    /**
48
     * @var StringFilterInterface
49
     */
50
    private $shortDescriptionFilter;
51
52
    /**
53
     * @var CalendarFactoryInterface
54
     */
55
    private $calendarFactory;
56
57
    /**
58
     * @var CdbXmlContactInfoImporterInterface
59
     */
60
    private $cdbXmlContactInfoImporter;
61
62
    /**
63
     * @param CdbXMLItemBaseImporter $cdbXMLItemBaseImporter
64
     * @param EventCdbIdExtractorInterface $cdbIdExtractor
65
     * @param PriceDescriptionParser $priceDescriptionParser
66
     * @param CalendarFactoryInterface $calendarFactory
67
     * @param CdbXmlContactInfoImporterInterface $cdbXmlContactInfoImporter
68
     */
69
    public function __construct(
70
        CdbXMLItemBaseImporter $cdbXMLItemBaseImporter,
71
        EventCdbIdExtractorInterface $cdbIdExtractor,
72
        PriceDescriptionParser $priceDescriptionParser,
73
        CalendarFactoryInterface $calendarFactory,
74
        CdbXmlContactInfoImporterInterface $cdbXmlContactInfoImporter
75
    ) {
76
        $this->cdbXMLItemBaseImporter = $cdbXMLItemBaseImporter;
77
        $this->cdbIdExtractor = $cdbIdExtractor;
78
        $this->priceDescriptionParser = $priceDescriptionParser;
79
        $this->calendarFactory = $calendarFactory;
80
        $this->cdbXmlContactInfoImporter = $cdbXmlContactInfoImporter;
81
82
        $consecutiveBlockOfTextFilter = new ConsecutiveBlockOfTextStringFilter();
83
84
        $this->longDescriptionFilter = new CombinedStringFilter();
85
        $this->longDescriptionFilter->addFilter(
86
            new StripSourceStringFilter()
87
        );
88
        $this->longDescriptionFilter->addFilter(
89
            $consecutiveBlockOfTextFilter
90
        );
91
        $this->longDescriptionFilter->addFilter(
92
            new BreakTagToNewlineStringFilter()
93
        );
94
        $this->longDescriptionFilter->addFilter(
95
            new StripSurroundingSpaceStringFilter()
96
        );
97
98
        $this->shortDescriptionFilter = $consecutiveBlockOfTextFilter;
99
    }
100
101
    /**
102
     * Imports a UDB2 event into a UDB3 JSON-LD document.
103
     *
104
     * @param \stdClass $base
105
     *   The JSON-LD document to start from.
106
     * @param \CultureFeed_Cdb_Item_Event $event
107
     *   The cultural event data from UDB2 to import.
108
     * @param PlaceServiceInterface $placeManager
109
     *   The manager from which to retrieve the JSON-LD of a place.
110
     * @param OrganizerServiceInterface $organizerManager
111
     *   The manager from which to retrieve the JSON-LD of an organizer.
112
     * @param SluggerInterface $slugger
113
     *   The slugger that's used to generate a sameAs reference.
114
     *
115
     * @return \stdClass
116
     *   The document with the UDB2 event data merged in.
117
     */
118
    public function documentWithCdbXML(
119
        $base,
120
        \CultureFeed_Cdb_Item_Event $event,
121
        PlaceServiceInterface $placeManager,
122
        OrganizerServiceInterface $organizerManager,
123
        SluggerInterface $slugger
124
    ) {
125
        $jsonLD = clone $base;
126
127
        /** @var \CultureFeed_Cdb_Data_EventDetail $detail */
128
        $detail = null;
129
130
        /** @var \CultureFeed_Cdb_Data_EventDetail[] $details */
131
        $details = $event->getDetails();
132
133
        foreach ($details as $languageDetail) {
134
            $language = $languageDetail->getLanguage();
135
136
            // The first language detail found will be used to retrieve
137
            // properties from which in UDB3 are not any longer considered
138
            // to be language specific.
139
            if (!$detail) {
140
                $detail = $languageDetail;
141
            }
142
143
            $jsonLD->name[$language] = $languageDetail->getTitle();
144
145
            $this->importDescription($languageDetail, $jsonLD, $language);
146
        }
147
148
        $this->cdbXMLItemBaseImporter->importAvailable($event, $jsonLD);
149
150
        $labelImporter = new LabelImporter();
151
        $labelImporter->importLabels($event, $jsonLD);
152
153
        $jsonLD->calendarSummary = $detail->getCalendarSummary();
154
155
        $this->importLocation($event, $placeManager, $jsonLD);
156
157
        $this->importOrganizer($event, $organizerManager, $jsonLD);
158
159 View Code Duplication
        if ($event->getContactInfo()) {
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...
160
            $this->cdbXmlContactInfoImporter->importBookingInfo(
161
                $jsonLD,
162
                $event->getContactInfo(),
163
                $detail->getPrice(),
164
                $event->getBookingPeriod()
165
            );
166
167
            $this->cdbXmlContactInfoImporter->importContactPoint(
168
                $jsonLD,
169
                $event->getContactInfo()
170
            );
171
        }
172
173
        $this->importPriceInfo($detail, $jsonLD);
174
175
        $this->importTerms($event, $jsonLD);
176
177
        $this->cdbXMLItemBaseImporter->importPublicationInfo($event, $jsonLD);
178
179
        $calendar = $this->calendarFactory->createFromCdbCalendar($event->getCalendar());
180
        $jsonLD = (object)array_merge((array)$jsonLD, $calendar->toJsonLd());
181
182
        $this->importTypicalAgeRange($event, $jsonLD);
183
184
        $this->importPerformers($detail, $jsonLD);
185
186
        $this->importLanguages($event, $jsonLD);
187
188
        $this->importUitInVlaanderenReference($event, $slugger, $jsonLD);
189
190
        $this->cdbXMLItemBaseImporter->importExternalId($event, $jsonLD);
191
192
        $this->importSeeAlso($event, $jsonLD);
193
194
        $this->cdbXMLItemBaseImporter->importWorkflowStatus($event, $jsonLD);
195
196
        $this->importAudience($event, $jsonLD);
197
198
        return $jsonLD;
199
    }
200
201
    /**
202
     * @param \CultureFeed_Cdb_Data_EventDetail $languageDetail
203
     * @param \stdClass $jsonLD
204
     * @param string $language
205
     */
206
    private function importDescription($languageDetail, $jsonLD, $language)
207
    {
208
        $longDescription = $languageDetail->getLongDescription();
209
210
        if ($longDescription) {
211
            $longDescription = $this->longDescriptionFilter->filter(
212
                $longDescription
213
            );
214
        }
215
216
        $descriptions = [];
217
218
        $shortDescription = $languageDetail->getShortDescription();
219
        if ($shortDescription) {
220
            $includeShortDescription = true;
221
222
            $shortDescription = $this->shortDescriptionFilter->filter(
223
                $shortDescription
224
            );
225
226
            if ($longDescription) {
227
                $includeShortDescription =
228
                    !$this->longDescriptionStartsWithShortDescription(
229
                        $longDescription,
230
                        $shortDescription
231
                    );
232
            }
233
234
            if ($includeShortDescription) {
235
                $descriptions[] = $shortDescription;
236
            }
237
        }
238
239
        if ($longDescription) {
240
            $descriptions[] = $longDescription;
241
        }
242
243
        $description = implode("\n\n", $descriptions);
244
245
        $jsonLD->description[$language] = $description;
246
    }
247
248
    /**
249
     * @param \CultureFeed_Cdb_Item_Event $event
250
     * @param PlaceServiceInterface $placeManager
251
     * @param \stdClass $jsonLD
252
     */
253
    private function importLocation(\CultureFeed_Cdb_Item_Event $event, PlaceServiceInterface $placeManager, $jsonLD)
254
    {
255
        $location = array();
256
        $location['@type'] = 'Place';
257
258
        $location_id = $this->cdbIdExtractor->getRelatedPlaceCdbId($event);
259
260
        if ($location_id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $location_id of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
261
            $location += (array)$placeManager->placeJSONLD($location_id);
262
        } else {
263
            $location_cdb = $event->getLocation();
264
            $location['name']['nl'] = $location_cdb->getLabel();
265
            $address = $location_cdb->getAddress()->getPhysicalAddress();
266
            if ($address) {
267
                $location['address'] = array(
268
                    'addressCountry' => $address->getCountry(),
269
                    'addressLocality' => $address->getCity(),
270
                    'postalCode' => $address->getZip(),
271
                    'streetAddress' =>
272
                        $address->getStreet() . ' ' . $address->getHouseNumber(
273
                        ),
274
                );
275
            }
276
        }
277
        $jsonLD->location = $location;
278
    }
279
280
    /**
281
     * @param \CultureFeed_Cdb_Item_Event $event
282
     * @param OrganizerServiceInterface $organizerManager
283
     * @param \stdClass $jsonLD
284
     */
285
    private function importOrganizer(
286
        \CultureFeed_Cdb_Item_Event $event,
287
        OrganizerServiceInterface $organizerManager,
288
        $jsonLD
289
    ) {
290
        $organizer = null;
291
        $organizer_id = $this->cdbIdExtractor->getRelatedOrganizerCdbId($event);
292
        $organizer_cdb = $event->getOrganiser();
293
        $contact_info_cdb = $event->getContactInfo();
294
295
        if ($organizer_id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $organizer_id of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
296
            $organizer = (array)$organizerManager->organizerJSONLD($organizer_id);
297
        } elseif ($organizer_cdb && $contact_info_cdb) {
298
            $organizer = array();
299
            $organizer['name'] = $organizer_cdb->getLabel();
300
301
            $emails_cdb = $contact_info_cdb->getMails();
302
            if (count($emails_cdb) > 0) {
303
                $organizer['email'] = array();
304
                foreach ($emails_cdb as $email) {
305
                    $organizer['email'][] = $email->getMailAddress();
306
                }
307
            }
308
309
            /** @var \CultureFeed_Cdb_Data_Phone[] $phones_cdb */
310
            $phones_cdb = $contact_info_cdb->getPhones();
311
            if (count($phones_cdb) > 0) {
312
                $organizer['phone'] = array();
313
                foreach ($phones_cdb as $phone) {
314
                    $organizer['phone'][] = $phone->getNumber();
315
                }
316
            }
317
        }
318
319
        if (!is_null($organizer)) {
320
            $organizer['@type'] = 'Organizer';
321
            $jsonLD->organizer = $organizer;
322
        }
323
    }
324
325
    /**
326
     * @param \CultureFeed_Cdb_Data_EventDetail $detail
327
     * @param \stdClass $jsonLD
328
     */
329
    private function importPriceInfo(
330
        \CultureFeed_Cdb_Data_EventDetail $detail,
331
        $jsonLD
332
    ) {
333
        $prices = array();
334
335
        $price = $detail->getPrice();
336
337
        if ($price) {
338
            $description = $price->getDescription();
339
340
            if ($description) {
341
                $prices = $this->priceDescriptionParser->parse($description);
342
            }
343
344
            // If price description was not interpretable, fall back to
345
            // price title and value.
346
            if (empty($prices) && $price->getValue() !== null) {
347
                $prices['Basistarief'] = floatval($price->getValue());
348
            }
349
        }
350
351
        if (!empty($prices)) {
352
            $priceInfo = array();
353
354
            /** @var \CultureFeed_Cdb_Data_Price $price */
355
            foreach ($prices as $title => $value) {
356
                $priceInfoItem = array(
357
                    'name' => $title,
358
                    'priceCurrency' => 'EUR',
359
                    'price' => $value,
360
                );
361
362
                $priceInfoItem['category'] = 'tariff';
363
364
                if ($priceInfoItem['name'] === 'Basistarief') {
365
                    $priceInfoItem['category'] = 'base';
366
                }
367
368
                $priceInfo[] = $priceInfoItem;
369
            }
370
371
            if (!empty($priceInfo)) {
372
                $jsonLD->priceInfo = $priceInfo;
373
            }
374
        }
375
    }
376
377
    /**
378
     * @param \CultureFeed_Cdb_Item_Event $event
379
     * @param \stdClass $jsonLD
380
     */
381
    private function importTerms(\CultureFeed_Cdb_Item_Event $event, $jsonLD)
382
    {
383
        $themeBlacklist = [
384
            'Thema onbepaald',
385
            'Meerder kunstvormen',
386
            'Meerdere filmgenres'
387
        ];
388
        $categories = array();
389 View Code Duplication
        foreach ($event->getCategories() as $category) {
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...
390
            /* @var \Culturefeed_Cdb_Data_Category $category */
391
            if ($category && !in_array($category->getName(), $themeBlacklist)) {
392
                $categories[] = array(
393
                    'label' => $category->getName(),
394
                    'domain' => $category->getType(),
395
                    'id' => $category->getId(),
396
                );
397
            }
398
        }
399
        $jsonLD->terms = $categories;
400
    }
401
402
    /**
403
     * @param \CultureFeed_Cdb_Item_Event $event
404
     * @param \stdClass $jsonLD
405
     */
406
    private function importTypicalAgeRange(\CultureFeed_Cdb_Item_Event $event, $jsonLD)
407
    {
408
        $ageFrom = $event->getAgeFrom();
409
410
        if (isset($ageFrom) && is_int($ageFrom)) {
411
            if ($ageFrom <= 12) {
412
                $jsonLD->typicalAgeRange = "{$ageFrom}-12";
413
            } else if ($ageFrom <= 18) {
414
                $jsonLD->typicalAgeRange = "{$ageFrom}-18";
415
            } else if ($ageFrom <= 99) {
416
                $jsonLD->typicalAgeRange = "{$ageFrom}-99";
417
            } else {
418
                $jsonLD->typicalAgeRange = '99-99';
419
            }
420
        }
421
    }
422
423
    /**
424
     * @param \CultureFeed_Cdb_Data_EventDetail $detail
425
     * @param \stdClass $jsonLD
426
     */
427
    private function importPerformers(\CultureFeed_Cdb_Data_EventDetail $detail, $jsonLD)
428
    {
429
        /** @var \CultureFeed_Cdb_Data_Performer $performer */
430
        $performers = $detail->getPerformers();
431
        if ($performers) {
432
            foreach ($performers as $performer) {
433
                if ($performer->getLabel()) {
434
                    $performerData = new \stdClass();
435
                    $performerData->name = $performer->getLabel();
436
                    $jsonLD->performer[] = $performerData;
437
                }
438
            }
439
        }
440
    }
441
442
    /**
443
     * @param \CultureFeed_Cdb_Item_Event $event
444
     * @param \stdClass $jsonLD
445
     */
446
    private function importLanguages(\CultureFeed_Cdb_Item_Event $event, $jsonLD)
447
    {
448
        /** @var \CultureFeed_Cdb_Data_Language $udb2Language */
449
        $languages = $event->getLanguages();
450
        if ($languages) {
451
            $jsonLD->language = [];
452
            foreach ($languages as $udb2Language) {
453
                $jsonLD->language[] = $udb2Language->getLanguage();
454
            }
455
            $jsonLD->language = array_unique($jsonLD->language);
456
        }
457
    }
458
459
    /**
460
     * @param \CultureFeed_Cdb_Item_Event $event
461
     * @param \stdClass $jsonLD
462
     */
463
    private function importSeeAlso(
464
        \CultureFeed_Cdb_Item_Event $event,
465
        \stdClass $jsonLD
466
    ) {
467
        if (!property_exists($jsonLD, 'seeAlso')) {
468
            $jsonLD->seeAlso = [];
469
        }
470
471
        // Add contact info url, if it's not for reservations.
472
        if ($contactInfo = $event->getContactInfo()) {
473
            /** @var \CultureFeed_Cdb_Data_Url[] $contactUrls */
474
            $contactUrls = $contactInfo->getUrls();
475
            if (is_array($contactUrls) && count($contactUrls) > 0) {
476
                foreach ($contactUrls as $contactUrl) {
477
                    if (!$contactUrl->isForReservations()) {
478
                        $jsonLD->seeAlso[] = $contactUrl->getUrl();
479
                    }
480
                }
481
            }
482
        }
483
    }
484
485
    /**
486
     * @param \CultureFeed_Cdb_Item_Event $event
487
     * @param SluggerInterface $slugger
488
     * @param \stdClass $jsonLD
489
     */
490
    private function importUitInVlaanderenReference(
491
        \CultureFeed_Cdb_Item_Event $event,
492
        SluggerInterface $slugger,
493
        $jsonLD
494
    ) {
495
496
        // Some events seem to not have a Dutch name, even though this is
497
        // required. If there's no Dutch name, we just leave the slug empty as
498
        // that seems to be the behaviour on http://m.uitinvlaanderen.be
499
        if (isset($jsonLD->name['nl'])) {
500
            $name = $jsonLD->name['nl'];
501
            $slug = $slugger->slug($name);
502
        } else {
503
            $slug = '';
504
        }
505
506
        $reference = 'http://www.uitinvlaanderen.be/agenda/e/' . $slug . '/' . $event->getCdbId();
507
508
509
        if (!property_exists($jsonLD, 'sameAs')) {
510
            $jsonLD->sameAs = [];
511
        }
512
513
        if (!in_array($reference, $jsonLD->sameAs)) {
514
            array_push($jsonLD->sameAs, $reference);
515
        }
516
    }
517
518
    /**
519
     * @param \CultureFeed_Cdb_Item_Event $event
520
     * @param \stdClass $jsonLD
521
     */
522
    private function importAudience(\CultureFeed_Cdb_Item_Event $event, \stdClass $jsonLD)
523
    {
524
        $eventIsPrivate = !!$event->isPrivate();
525
        $eventTargetsEducation = $eventIsPrivate && $event->getCategories()->hasCategory('2.1.3.0.0');
526
527
        $audienceType = $eventTargetsEducation ? 'education' : ($eventIsPrivate ? 'members' : 'everyone');
528
        $audience = new Audience(AudienceType::fromNative($audienceType));
529
530
        $jsonLD->audience = $audience->serialize();
531
    }
532
533
    /**
534
     * @param string $longDescription
535
     * @param string $shortDescription
536
     * @return bool
537
     */
538
    private function longDescriptionStartsWithShortDescription(
539
        $longDescription,
540
        $shortDescription
541
    ) {
542
        $longDescription = strip_tags(html_entity_decode($longDescription));
543
544
        return 0 === strncmp($longDescription, $shortDescription, mb_strlen($shortDescription));
545
    }
546
}
547