Completed
Pull Request — master (#107)
by Kristof
38:33 queued 15:13
created

CdbXMLImporter::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
/**
3
 * @file
4
 */
5
6
namespace CultuurNet\UDB3\Event\ReadModel\JSONLD;
7
8
use CultuurNet\UDB3\Cdb\DateTimeFactory;
9
use CultuurNet\UDB3\LabelCollection;
10
use CultuurNet\UDB3\Offer\ReadModel\JSONLD\CdbXMLItemBaseImporter;
11
use CultuurNet\UDB3\SluggerInterface;
12
use CultuurNet\UDB3\StringFilter\StringFilterInterface;
13
14
/**
15
 * Takes care of importing cultural events in the CdbXML format (UDB2)
16
 * into a UDB3 JSON-LD document.
17
 */
18
class CdbXMLImporter
19
{
20
    /**
21
     * @var CdbXMLItemBaseImporter
22
     */
23
    private $cdbXMLItemBaseImporter;
24
25
    /**
26
     * @param CdbXMLItemBaseImporter $dbXMLItemBaseImporter
27
     */
28
    public function __construct(CdbXMLItemBaseImporter $dbXMLItemBaseImporter)
29
    {
30
        $this->cdbXMLItemBaseImporter = $dbXMLItemBaseImporter;
31
    }
32
33
    /**
34
     * @var StringFilterInterface[]
35
     */
36
    private $descriptionFilters = [];
37
38
    /**
39
     * Imports a UDB2 event into a UDB3 JSON-LD document.
40
     *
41
     * @param \stdClass $base
42
     *   The JSON-LD document to start from.
43
     * @param \CultureFeed_Cdb_Item_Event $event
44
     *   The cultural event data from UDB2 to import.
45
     * @param PlaceServiceInterface $placeManager
46
     *   The manager from which to retrieve the JSON-LD of a place.
47
     * @param OrganizerServiceInterface $organizerManager
48
     *   The manager from which to retrieve the JSON-LD of an organizer.
49
     * @param SluggerInterface $slugger
50
     *   The slugger that's used to generate a sameAs reference.
51
     *
52
     * @return \stdClass
53
     *   The document with the UDB2 event data merged in.
54
     */
55
    public function documentWithCdbXML(
56
        $base,
57
        \CultureFeed_Cdb_Item_Event $event,
58
        PlaceServiceInterface $placeManager,
59
        OrganizerServiceInterface $organizerManager,
60
        SluggerInterface $slugger
61
    ) {
62
        $jsonLD = clone $base;
63
64
        /** @var \CultureFeed_Cdb_Data_EventDetail $detail */
65
        $detail = null;
66
67
        /** @var \CultureFeed_Cdb_Data_EventDetail[] $details */
68
        $details = $event->getDetails();
69
70
        foreach ($details as $languageDetail) {
71
            $language = $languageDetail->getLanguage();
72
73
            // The first language detail found will be used to retrieve
74
            // properties from which in UDB3 are not any longer considered
75
            // to be language specific.
76
            if (!$detail) {
77
                $detail = $languageDetail;
78
            }
79
80
            $jsonLD->name[$language] = $languageDetail->getTitle();
81
82
            $this->importDescription($languageDetail, $jsonLD, $language);
83
        }
84
85
        $this->cdbXMLItemBaseImporter->importAvailable($event, $jsonLD);
86
87
        $this->importPicture($detail, $jsonLD);
88
89
        $this->importLabels($event, $jsonLD);
90
91
        $jsonLD->calendarSummary = $detail->getCalendarSummary();
92
93
        $this->importLocation($event, $placeManager, $jsonLD);
94
95
        $this->importOrganizer($event, $organizerManager, $jsonLD);
96
97
        $this->importBookingInfo($event, $detail, $jsonLD);
98
99
        $this->importTerms($event, $jsonLD);
100
101
        $this->cdbXMLItemBaseImporter->importPublicationInfo($event, $jsonLD);
102
103
        $this->importCalendar($event, $jsonLD);
104
105
        $this->importTypicalAgeRange($event, $jsonLD);
106
107
        $this->importPerformers($detail, $jsonLD);
108
109
        $this->importLanguages($event, $jsonLD);
110
111
        $this->importUitInVlaanderenReference($event, $slugger, $jsonLD);
112
113
        $this->cdbXMLItemBaseImporter->importExternalId($event, $jsonLD);
114
115
        $this->importSeeAlso($event, $jsonLD);
116
117
        $this->importContactPoint($event, $jsonLD);
118
119
        return $jsonLD;
120
    }
121
122
    /**
123
     * @param StringFilterInterface $filter
124
     */
125
    public function addDescriptionFilter(StringFilterInterface $filter)
126
    {
127
        $this->descriptionFilters[] = $filter;
128
    }
129
130
    /**
131
     * @param int $unixTime
132
     * @return \DateTime
133
     */
134
    private function dateFromUdb2UnixTime($unixTime)
135
    {
136
        $dateTime = new \DateTime(
137
            '@' . $unixTime,
138
            new \DateTimeZone('Europe/Brussels')
139
        );
140
141
        return $dateTime;
142
    }
143
144
    /**
145
     * @param \CultureFeed_Cdb_Data_EventDetail $languageDetail
146
     * @param \stdClass $jsonLD
147
     * @param string $language
148
     */
149
    private function importDescription($languageDetail, $jsonLD, $language)
150
    {
151
        $descriptions = [
152
            $languageDetail->getShortDescription(),
153
            $languageDetail->getLongDescription()
154
        ];
155
        $descriptions = array_filter($descriptions);
156
        $description = implode('<br/>', $descriptions);
157
158
        foreach ($this->descriptionFilters as $descriptionFilter) {
159
            $description = $descriptionFilter->filter($description);
160
        };
161
162
        $jsonLD->description[$language] = $description;
163
    }
164
165
    /**
166
     * @param \CultureFeed_Cdb_Item_Event $event
167
     * @param $jsonLD
168
     */
169
    private function importLabels(\CultureFeed_Cdb_Item_Event $event, $jsonLD)
170
    {
171
        $labels = array_filter(
172
            array_values($event->getKeywords()),
173
            function ($keyword) {
174
                return (strlen(trim($keyword)) > 0);
175
            }
176
        );
177
        // Ensure keys are continuous after the filtering was applied, otherwise
178
        // JSON-encoding the array will result in an object.
179
        $labels = array_values($labels);
180
181
        // Create a label collection to get rid of duplicates.
182
        $labelCollection = LabelCollection::fromStrings($labels);
183
184
        if (count($labelCollection) > 0) {
185
            $jsonLD->labels = $labelCollection->toStrings();
186
        }
187
    }
188
189
    /**
190
     * @param \CultureFeed_Cdb_Data_EventDetail $detail
191
     * @param \stdClass $jsonLD
192
     */
193
    private function importPicture($detail, $jsonLD)
194
    {
195
        $pictures = $detail->getMedia()->byMediaType(
196
            \CultureFeed_Cdb_Data_File::MEDIA_TYPE_PHOTO
197
        );
198
199
        $pictures->rewind();
200
        $picture = count($pictures) > 0 ? $pictures->current() : null;
201
        if ($picture) {
202
            $jsonLD->image = $picture->getHLink();
203
        }
204
    }
205
206
    /**
207
     * @param \CultureFeed_Cdb_Item_Event $event
208
     * @param PlaceServiceInterface $placeManager
209
     * @param \stdClass $jsonLD
210
     */
211
    private function importLocation(\CultureFeed_Cdb_Item_Event $event, PlaceServiceInterface $placeManager, $jsonLD)
212
    {
213
        $location = array();
214
        $location['@type'] = 'Place';
215
216
        $location_cdb = $event->getLocation();
217
        $location_id = $location_cdb->getCdbid();
218
219
        if ($location_id) {
220
            $location += (array)$placeManager->placeJSONLD($location_id);
221
        } else {
222
            $location['name'] = $location_cdb->getLabel();
223
            $address = $location_cdb->getAddress()->getPhysicalAddress();
224
            if ($address) {
225
                $location['address'] = array(
226
                    'addressCountry' => $address->getCountry(),
227
                    'addressLocality' => $address->getCity(),
228
                    'postalCode' => $address->getZip(),
229
                    'streetAddress' =>
230
                        $address->getStreet() . ' ' . $address->getHouseNumber(
231
                        ),
232
                );
233
            }
234
        }
235
        $jsonLD->location = $location;
236
    }
237
238
    /**
239
     * @param \CultureFeed_Cdb_Item_Event $event
240
     * @param OrganizerServiceInterface $organizerManager
241
     * @param \stdClass $jsonLD
242
     */
243
    private function importOrganizer(
244
        \CultureFeed_Cdb_Item_Event $event,
245
        OrganizerServiceInterface $organizerManager,
246
        $jsonLD
247
    ) {
248
// Organizer.
249
        $organizer_cdb = $event->getOrganiser();
250
        $contact_info_cdb = $event->getContactInfo();
251
252
        if ($organizer_cdb && $contact_info_cdb) {
253
            $organizer_id = $organizer_cdb->getCdbid();
254
            if ($organizer_id) {
255
                $organizer = (array)$organizerManager->organizerJSONLD($organizer_id);
256
            } else {
257
                $organizer = array();
258
                $organizer['name'] = $organizer_cdb->getLabel();
259
260
                $emails_cdb = $contact_info_cdb->getMails();
261
                if (count($emails_cdb) > 0) {
262
                    $organizer['email'] = array();
263
                    foreach ($emails_cdb as $email) {
264
                        $organizer['email'][] = $email->getMailAddress();
265
                    }
266
                }
267
268
                $phones_cdb = $contact_info_cdb->getPhones();
269
                if (count($phones_cdb) > 0) {
270
                    $organizer['phone'] = array();
271
                    foreach ($phones_cdb as $phone) {
272
                        $organizer['phone'][] = $phone->getNumber();
273
                    }
274
                }
275
            }
276
            $organizer['@type'] = 'Organizer';
277
            $jsonLD->organizer = $organizer;
278
        }
279
    }
280
281
    /**
282
     * @param \CultureFeed_Cdb_Data_EventDetail $detail
283
     * @param \stdClass $jsonLD
284
     */
285
    private function importBookingInfo(
286
        \CultureFeed_Cdb_Item_Event $event,
287
        \CultureFeed_Cdb_Data_EventDetail $detail,
288
        $jsonLD
289
    ) {
290
        $price = $detail->getPrice();
291
292
        if ($price) {
293
            $jsonLD->bookingInfo = array();
294
            // Booking info.
295
            $bookingInfo = array();
296
            if ($price->getDescription()) {
297
                $bookingInfo['description'] = $price->getDescription();
298
            }
299
            if ($price->getTitle()) {
300
                $bookingInfo['name'] = $price->getTitle();
301
            }
302
            if ($price->getValue() !== null) {
303
                $bookingInfo['priceCurrency'] = 'EUR';
304
                $bookingInfo['price'] = floatval($price->getValue());
305
            }
306
            if ($bookingPeriod = $event->getBookingPeriod()) {
307
                $startDate = $this->dateFromUdb2UnixTime($bookingPeriod->getDateFrom());
308
                $endDate = $this->dateFromUdb2UnixTime($bookingPeriod->getDateTill());
309
310
                $bookingInfo['availabilityStarts'] = $startDate->format('c');
311
                $bookingInfo['availabilityEnds'] = $endDate->format('c');
312
            }
313
314
            // Add reservation URL
315
            if ($contactInfo = $event->getContactInfo()) {
316
                if ($bookingUrl = $contactInfo->getReservationUrl()) {
317
                    $bookingInfo['url'] = $bookingUrl;
318
                }
319
            }
320
321
            $jsonLD->bookingInfo[] = $bookingInfo;
322
        }
323
    }
324
325
    /**
326
     * @param \CultureFeed_Cdb_Item_Event $event
327
     * @param \stdClass $jsonLD
328
     */
329
    private function importContactPoint(
330
        \CultureFeed_Cdb_Item_Event $event,
331
        \stdClass $jsonLD
332
    ) {
333
        $contactInfo = $event->getContactInfo();
334
335
        if ($contactInfo) {
336
            $reservationContactPoint = array();
337
            $leftoverContactPoint = array();
338
339
            foreach ($contactInfo->getMails() as $email) {
340
                /** @var \CultureFeed_Cdb_Data_Mail $email */
341
                $emailAddress = $email->getMailAddress();
342
343
                if ($email->isForReservations()) {
344
                    $reservationContactPoint['email'][] = $emailAddress;
345
                } else {
346
                    $leftoverContactPoint['email'][] = $emailAddress;
347
                }
348
            }
349
350
            foreach ($contactInfo->getPhones() as $phone) {
351
                /** @var \CultureFeed_Cdb_Data_Phone $phone */
352
                $phoneNumber = $phone->getNumber();
353
354
                if ($phone->isForReservations()) {
355
                    $reservationContactPoint['telephone'][] = $phoneNumber;
356
                } else {
357
                    $leftoverContactPoint['telephone'][] = $phoneNumber;
358
                }
359
            }
360
361
            array_filter($reservationContactPoint);
362
            if (count($reservationContactPoint) > 0) {
363
                $reservationContactPoint['contactType'] = "Reservations";
364
                $jsonLD->contactPoint[] = $reservationContactPoint;
365
            }
366
367
            array_filter($leftoverContactPoint);
368
            if (count($leftoverContactPoint) > 0) {
369
                $jsonLD->contactPoint[] = $leftoverContactPoint;
370
            }
371
        }
372
    }
373
374
    /**
375
     * @param \CultureFeed_Cdb_Item_Event $event
376
     * @param \stdClass $jsonLD
377
     */
378
    private function importTerms(\CultureFeed_Cdb_Item_Event $event, $jsonLD)
379
    {
380
        $themeBlacklist = [
381
            'Thema onbepaald',
382
            'Meerder kunstvormen',
383
            'Meerdere filmgenres'
384
        ];
385
        $categories = array();
386 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...
387
            /* @var \Culturefeed_Cdb_Data_Category $category */
388
            if ($category && !in_array($category->getName(), $themeBlacklist)) {
389
                $categories[] = array(
390
                    'label' => $category->getName(),
391
                    'domain' => $category->getType(),
392
                    'id' => $category->getId(),
393
                );
394
            }
395
        }
396
        $jsonLD->terms = $categories;
397
    }
398
399
    /**
400
     * @param \CultureFeed_Cdb_Item_Event $event
401
     * @param \stdClass $jsonLD
402
     */
403
    private function importCalendar(\CultureFeed_Cdb_Item_Event $event, $jsonLD)
404
    {
405
        // To render the front-end we make a distinction between 4 calendar types
406
        // Permanent and Periodic map directly to the Cdb calendar classes
407
        // Simple timestamps are divided into single and multiple
408
        $calendarType = 'unknown';
409
        $calendar = $event->getCalendar();
410
411
        if ($calendar instanceof \CultureFeed_Cdb_Data_Calendar_Permanent) {
412
            $calendarType = 'permanent';
413
        } elseif ($calendar instanceof \CultureFeed_Cdb_Data_Calendar_PeriodList) {
414
            $calendarType = 'periodic';
415
            $calendar->rewind();
416
            $firstCalendarItem = $calendar->current();
417
            $startDateString = $firstCalendarItem->getDateFrom() . 'T00:00:00';
418
            $startDate = DateTimeFactory::dateTimeFromDateString($startDateString);
419
420 View Code Duplication
            if (iterator_count($calendar) > 1) {
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...
421
                $periodArray = iterator_to_array($calendar);
422
                $lastCalendarItem = end($periodArray);
423
            } else {
424
                $lastCalendarItem = $firstCalendarItem;
425
            }
426
427
            $endDateString = $lastCalendarItem->getDateTo() . 'T00:00:00';
428
            $endDate = DateTimeFactory::dateTimeFromDateString($endDateString);
429
430
            $jsonLD->startDate = $startDate->format('c');
431
            $jsonLD->endDate = $endDate->format('c');
432
        } elseif ($calendar instanceof \CultureFeed_Cdb_Data_Calendar_TimestampList) {
433
            $calendarType = 'single';
434
            $calendar->rewind();
435
            /** @var \CultureFeed_Cdb_Data_Calendar_Timestamp $firstCalendarItem */
436
            $firstCalendarItem = $calendar->current();
437
            if ($firstCalendarItem->getStartTime()) {
438
                $dateString =
439
                    $firstCalendarItem->getDate() . 'T' . $firstCalendarItem->getStartTime();
440
            } else {
441
                $dateString = $firstCalendarItem->getDate() . 'T00:00:00';
442
            }
443
444
            $startDate = DateTimeFactory::dateTimeFromDateString($dateString);
445
446 View Code Duplication
            if (iterator_count($calendar) > 1) {
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...
447
                $periodArray = iterator_to_array($calendar);
448
                $lastCalendarItem = end($periodArray);
449
            } else {
450
                $lastCalendarItem = $firstCalendarItem;
451
            }
452
453
            $endDateString = null;
454
            if ($lastCalendarItem->getEndTime()) {
455
                $endDateString =
456
                    $lastCalendarItem->getDate() . 'T' . $lastCalendarItem->getEndTime();
457
            } else {
458
                if (iterator_count($calendar) > 1) {
459
                    $endDateString = $lastCalendarItem->getDate() . 'T00:00:00';
460
                }
461
            }
462
463
            if ($endDateString) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $endDateString 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...
464
                $endDate = DateTimeFactory::dateTimeFromDateString($endDateString);
465
                $jsonLD->endDate = $endDate->format('c');
466
467
                if ($startDate->format('Ymd') != $endDate->format('Ymd')) {
468
                    $calendarType = 'multiple';
469
                }
470
            }
471
472
            $jsonLD->startDate = $startDate->format('c');
473
        }
474
475
        $jsonLD->calendarType = $calendarType;
476
    }
477
478
    /**
479
     * @param \CultureFeed_Cdb_Item_Event $event
480
     * @param \stdClass $jsonLD
481
     */
482
    private function importTypicalAgeRange(\CultureFeed_Cdb_Item_Event $event, $jsonLD)
483
    {
484
        $ageFrom = $event->getAgeFrom();
485
        if ($ageFrom) {
486
            $jsonLD->typicalAgeRange = "{$ageFrom}-";
487
        }
488
    }
489
490
    /**
491
     * @param \CultureFeed_Cdb_Data_EventDetail $detail
492
     * @param \stdClass $jsonLD
493
     */
494
    private function importPerformers(\CultureFeed_Cdb_Data_EventDetail $detail, $jsonLD)
495
    {
496
        /** @var \CultureFeed_Cdb_Data_Performer $performer */
497
        $performers = $detail->getPerformers();
498
        if ($performers) {
499
            foreach ($performers as $performer) {
500
                if ($performer->getLabel()) {
501
                    $performerData = new \stdClass();
502
                    $performerData->name = $performer->getLabel();
503
                    $jsonLD->performer[] = $performerData;
504
                }
505
            }
506
        }
507
    }
508
509
    /**
510
     * @param \CultureFeed_Cdb_Item_Event $event
511
     * @param \stdClass $jsonLD
512
     */
513
    private function importLanguages(\CultureFeed_Cdb_Item_Event $event, $jsonLD)
514
    {
515
        /** @var \CultureFeed_Cdb_Data_Language $udb2Language */
516
        $languages = $event->getLanguages();
517
        if ($languages) {
518
            $jsonLD->language = [];
519
            foreach ($languages as $udb2Language) {
520
                $jsonLD->language[] = $udb2Language->getLanguage();
521
            }
522
            $jsonLD->language = array_unique($jsonLD->language);
523
        }
524
    }
525
526
    /**
527
     * @param \CultureFeed_Cdb_Item_Event $event
528
     * @param \stdClass $jsonLD
529
     */
530
    private function importSeeAlso(
531
        \CultureFeed_Cdb_Item_Event $event,
532
        \stdClass $jsonLD
533
    ) {
534
        if (!property_exists($jsonLD, 'seeAlso')) {
535
            $jsonLD->seeAlso = [];
536
        }
537
538
        // Add contact info url, if it's not for reservations.
539
        if ($contactInfo = $event->getContactInfo()) {
540
            /** @var \CultureFeed_Cdb_Data_Url[] $contactUrls */
541
            $contactUrls = $contactInfo->getUrls();
542
            if (is_array($contactUrls) && count($contactUrls) > 0) {
543
                foreach ($contactUrls as $contactUrl) {
544
                    if (!$contactUrl->isForReservations()) {
545
                        $jsonLD->seeAlso[] = $contactUrl->getUrl();
546
                    }
547
                }
548
            }
549
        }
550
    }
551
552
    /**
553
     * @param \CultureFeed_Cdb_Item_Event $event
554
     * @param SluggerInterface $slugger
555
     * @param \stdClass $jsonLD
556
     */
557
    private function importUitInVlaanderenReference(
558
        \CultureFeed_Cdb_Item_Event $event,
559
        SluggerInterface $slugger,
560
        $jsonLD
561
    ) {
562
563
        // Some events seem to not have a Dutch name, even though this is
564
        // required. If there's no Dutch name, we just leave the slug empty as
565
        // that seems to be the behaviour on http://m.uitinvlaanderen.be
566
        if (isset($jsonLD->name['nl'])) {
567
            $name = $jsonLD->name['nl'];
568
            $slug = $slugger->slug($name);
569
        } else {
570
            $slug = '';
571
        }
572
573
        $reference = 'http://www.uitinvlaanderen.be/agenda/e/' . $slug . '/' . $event->getCdbId();
574
575
576
        if (!property_exists($jsonLD, 'sameAs')) {
577
            $jsonLD->sameAs = [];
578
        }
579
580
        if (!in_array($reference, $jsonLD->sameAs)) {
581
            array_push($jsonLD->sameAs, $reference);
582
        }
583
    }
584
}
585