1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace CultuurNet\UDB3; |
4
|
|
|
|
5
|
|
|
use Broadway\Serializer\SerializableInterface; |
6
|
|
|
use CultuurNet\UDB3\Calendar\OpeningHour; |
7
|
|
|
use CultuurNet\UDB3\Model\ValueObject\Calendar\Calendar as Udb3ModelCalendar; |
8
|
|
|
use CultuurNet\UDB3\Model\ValueObject\Calendar\CalendarWithDateRange; |
9
|
|
|
use CultuurNet\UDB3\Model\ValueObject\Calendar\CalendarWithOpeningHours; |
10
|
|
|
use CultuurNet\UDB3\Model\ValueObject\Calendar\CalendarWithSubEvents; |
11
|
|
|
use CultuurNet\UDB3\Model\ValueObject\Calendar\DateRange; |
12
|
|
|
use CultuurNet\UDB3\Model\ValueObject\Calendar\OpeningHours\OpeningHour as Udb3ModelOpeningHour; |
13
|
|
|
use DateTime; |
14
|
|
|
use DateTimeInterface; |
15
|
|
|
use DateTimeZone; |
16
|
|
|
use InvalidArgumentException; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* Calendar for events and places. |
20
|
|
|
* @todo Replace by CultuurNet\UDB3\Model\ValueObject\Calendar\Calendar. |
21
|
|
|
*/ |
22
|
|
|
class Calendar implements CalendarInterface, JsonLdSerializableInterface, SerializableInterface |
23
|
|
|
{ |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* @var CalendarType |
27
|
|
|
*/ |
28
|
|
|
protected $type = null; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* @var DateTimeInterface |
32
|
|
|
*/ |
33
|
|
|
protected $startDate = null; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* @var DateTimeInterface |
37
|
|
|
*/ |
38
|
|
|
protected $endDate = null; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* @var Timestamp[] |
42
|
|
|
*/ |
43
|
|
|
protected $timestamps = array(); |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* @var OpeningHour[] |
47
|
|
|
*/ |
48
|
|
|
protected $openingHours = array(); |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* @param CalendarType $type |
52
|
|
|
* @param DateTimeInterface|null $startDate |
53
|
|
|
* @param DateTimeInterface|null $endDate |
54
|
|
|
* @param Timestamp[] $timestamps |
55
|
|
|
* @param OpeningHour[] $openingHours |
56
|
|
|
*/ |
57
|
|
|
public function __construct( |
58
|
|
|
CalendarType $type, |
59
|
|
|
DateTimeInterface $startDate = null, |
60
|
|
|
DateTimeInterface $endDate = null, |
61
|
|
|
array $timestamps = array(), |
62
|
|
|
array $openingHours = array() |
63
|
|
|
) { |
64
|
|
|
if (($type->is(CalendarType::SINGLE()) || $type->is(CalendarType::MULTIPLE())) && (empty($timestamps))) { |
65
|
|
|
throw new \UnexpectedValueException('A single or multiple calendar should have timestamps.'); |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
if ($type->is(CalendarType::PERIODIC()) && (empty($startDate) || empty($endDate))) { |
69
|
|
|
throw new \UnexpectedValueException('A period should have a start- and end-date.'); |
70
|
|
|
} |
71
|
|
|
|
72
|
|
|
foreach ($timestamps as $timestamp) { |
73
|
|
|
if (!is_a($timestamp, Timestamp::class)) { |
74
|
|
|
throw new \InvalidArgumentException('Timestamps should have type TimeStamp.'); |
75
|
|
|
} |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
foreach ($openingHours as $openingHour) { |
79
|
|
|
if (!is_a($openingHour, OpeningHour::class)) { |
80
|
|
|
throw new \InvalidArgumentException('OpeningHours should have type OpeningHour.'); |
81
|
|
|
} |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
$this->type = $type->toNative(); |
85
|
|
|
$this->startDate = $startDate; |
86
|
|
|
$this->endDate = $endDate; |
87
|
|
|
$this->openingHours = $openingHours; |
88
|
|
|
|
89
|
|
|
usort($timestamps, function (Timestamp $timestamp, Timestamp $otherTimestamp) { |
90
|
|
|
return $timestamp->getStartDate() <=> $otherTimestamp->getStartDate(); |
91
|
|
|
}); |
92
|
|
|
|
93
|
|
|
$this->timestamps = $timestamps; |
94
|
|
|
|
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* @inheritdoc |
99
|
|
|
*/ |
100
|
|
|
public function getType() |
101
|
|
|
{ |
102
|
|
|
return CalendarType::fromNative($this->type); |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* {@inheritdoc} |
107
|
|
|
*/ |
108
|
|
|
public function serialize() |
109
|
|
|
{ |
110
|
|
|
$serializedTimestamps = array_map( |
111
|
|
|
function (Timestamp $timestamp) { |
112
|
|
|
return $timestamp->serialize(); |
113
|
|
|
}, |
114
|
|
|
$this->timestamps |
115
|
|
|
); |
116
|
|
|
|
117
|
|
|
$serializedOpeningHours = array_map( |
118
|
|
|
function (OpeningHour $openingHour) { |
119
|
|
|
return $openingHour->serialize(); |
120
|
|
|
}, |
121
|
|
|
$this->openingHours |
122
|
|
|
); |
123
|
|
|
|
124
|
|
|
$calendar = [ |
125
|
|
|
'type' => $this->type, |
126
|
|
|
]; |
127
|
|
|
|
128
|
|
|
empty($this->startDate) ?: $calendar['startDate'] = $this->startDate->format(DateTime::ATOM); |
129
|
|
|
empty($this->endDate) ?: $calendar['endDate'] = $this->endDate->format(DateTime::ATOM); |
130
|
|
|
empty($serializedTimestamps) ?: $calendar['timestamps'] = $serializedTimestamps; |
131
|
|
|
empty($serializedOpeningHours) ?: $calendar['openingHours'] = $serializedOpeningHours; |
132
|
|
|
|
133
|
|
|
return $calendar; |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
/** |
137
|
|
|
* {@inheritdoc} |
138
|
|
|
*/ |
139
|
|
|
public static function deserialize(array $data) |
140
|
|
|
{ |
141
|
|
|
$calendarType = CalendarType::fromNative($data['type']); |
142
|
|
|
|
143
|
|
|
// Backwards compatibility for serialized single or multiple calendar types that are missing timestamps but do |
144
|
|
|
// have a start and end date. |
145
|
|
|
$defaultTimeStamps = []; |
146
|
|
|
if ($calendarType->sameValueAs(CalendarType::SINGLE()) || $calendarType->sameValueAs(CalendarType::MULTIPLE())) { |
147
|
|
|
$defaultTimeStampStartDate = !empty($data['startDate']) ? self::deserializeDateTime($data['startDate']) : null; |
148
|
|
|
$defaultTimeStampEndDate = !empty($data['endDate']) ? self::deserializeDateTime($data['endDate']) : $defaultTimeStampStartDate; |
149
|
|
|
$defaultTimeStamp = $defaultTimeStampStartDate && $defaultTimeStampEndDate ? new Timestamp($defaultTimeStampStartDate, $defaultTimeStampEndDate) : null; |
150
|
|
|
$defaultTimeStamps = $defaultTimeStamp ? [$defaultTimeStamp] : []; |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
return new static( |
154
|
|
|
$calendarType, |
155
|
|
|
!empty($data['startDate']) ? self::deserializeDateTime($data['startDate']) : null, |
156
|
|
|
!empty($data['endDate']) ? self::deserializeDateTime($data['endDate']) : null, |
157
|
|
|
!empty($data['timestamps']) ? array_map( |
158
|
|
|
function ($timestamp) { |
159
|
|
|
return Timestamp::deserialize($timestamp); |
160
|
|
|
}, |
161
|
|
|
$data['timestamps'] |
162
|
|
|
) : $defaultTimeStamps, |
163
|
|
|
!empty($data['openingHours']) ? array_map( |
164
|
|
|
function ($openingHour) { |
165
|
|
|
return OpeningHour::deserialize($openingHour); |
166
|
|
|
}, |
167
|
|
|
$data['openingHours'] |
168
|
|
|
) : [] |
169
|
|
|
); |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
/** |
173
|
|
|
* This deserialization function takes into account old data that might be missing a timezone. |
174
|
|
|
* It will fall back to creating a DateTime object and assume Brussels. |
175
|
|
|
* If this still fails an error will be thrown. |
176
|
|
|
* |
177
|
|
|
* @param $dateTimeData |
178
|
|
|
* @return DateTime |
179
|
|
|
* |
180
|
|
|
* @throws InvalidArgumentException |
181
|
|
|
*/ |
182
|
|
|
private static function deserializeDateTime($dateTimeData) |
183
|
|
|
{ |
184
|
|
|
$dateTime = DateTime::createFromFormat(DateTime::ATOM, $dateTimeData); |
185
|
|
|
|
186
|
|
|
if ($dateTime === false) { |
187
|
|
|
$dateTime = DateTime::createFromFormat('Y-m-d\TH:i:s', $dateTimeData, new DateTimeZone('Europe/Brussels')); |
188
|
|
|
|
189
|
|
|
if (!$dateTime) { |
190
|
|
|
throw new InvalidArgumentException('Invalid date string provided for timestamp, ISO8601 expected!'); |
191
|
|
|
} |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
return $dateTime; |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
/** |
198
|
|
|
* @inheritdoc |
199
|
|
|
*/ |
200
|
|
View Code Duplication |
public function getStartDate() |
|
|
|
|
201
|
|
|
{ |
202
|
|
|
$timestamps = $this->getTimestamps(); |
203
|
|
|
|
204
|
|
|
if (empty($timestamps)) { |
205
|
|
|
return $this->startDate; |
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
$startDate = null; |
209
|
|
|
foreach ($timestamps as $timestamp) { |
210
|
|
|
if ($startDate === null || $timestamp->getStartDate() < $startDate) { |
211
|
|
|
$startDate = $timestamp->getStartDate(); |
212
|
|
|
} |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
return $startDate; |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
/** |
219
|
|
|
* @inheritdoc |
220
|
|
|
*/ |
221
|
|
View Code Duplication |
public function getEndDate() |
|
|
|
|
222
|
|
|
{ |
223
|
|
|
$timestamps = $this->getTimestamps(); |
224
|
|
|
|
225
|
|
|
if (empty($timestamps)) { |
226
|
|
|
return $this->endDate; |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
$endDate = null; |
230
|
|
|
foreach ($this->getTimestamps() as $timestamp) { |
231
|
|
|
if ($endDate === null || $timestamp->getEndDate() > $endDate) { |
232
|
|
|
$endDate = $timestamp->getEndDate(); |
233
|
|
|
} |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
return $endDate; |
237
|
|
|
} |
238
|
|
|
|
239
|
|
|
/** |
240
|
|
|
* @inheritdoc |
241
|
|
|
*/ |
242
|
|
|
public function getOpeningHours() |
243
|
|
|
{ |
244
|
|
|
return $this->openingHours; |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
/** |
248
|
|
|
* @inheritdoc |
249
|
|
|
*/ |
250
|
|
|
public function getTimestamps() |
251
|
|
|
{ |
252
|
|
|
return $this->timestamps; |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
/** |
256
|
|
|
* Return the jsonLD version of a calendar. |
257
|
|
|
*/ |
258
|
|
|
public function toJsonLd() |
259
|
|
|
{ |
260
|
|
|
$jsonLd = []; |
261
|
|
|
|
262
|
|
|
$jsonLd['calendarType'] = $this->getType()->toNative(); |
263
|
|
|
|
264
|
|
|
$startDate = $this->getStartDate(); |
265
|
|
|
$endDate = $this->getEndDate(); |
266
|
|
|
if ($startDate !== null) { |
267
|
|
|
$jsonLd['startDate'] = $startDate->format(DateTime::ATOM); |
268
|
|
|
} |
269
|
|
|
if ($endDate !== null) { |
270
|
|
|
$jsonLd['endDate'] = $endDate->format(DateTime::ATOM); |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
$timestamps = $this->getTimestamps(); |
274
|
|
|
if (!empty($timestamps)) { |
275
|
|
|
$jsonLd['subEvent'] = array(); |
276
|
|
|
foreach ($timestamps as $timestamp) { |
277
|
|
|
$jsonLd['subEvent'][] = array( |
278
|
|
|
'@type' => 'Event', |
279
|
|
|
'startDate' => $timestamp->getStartDate()->format(DateTime::ATOM), |
280
|
|
|
'endDate' => $timestamp->getEndDate()->format(DateTime::ATOM), |
281
|
|
|
); |
282
|
|
|
} |
283
|
|
|
} |
284
|
|
|
|
285
|
|
|
$openingHours = $this->getOpeningHours(); |
286
|
|
|
if (!empty($openingHours)) { |
287
|
|
|
$jsonLd['openingHours'] = array(); |
288
|
|
|
foreach ($openingHours as $openingHour) { |
289
|
|
|
$jsonLd['openingHours'][] = $openingHour->serialize(); |
290
|
|
|
} |
291
|
|
|
} |
292
|
|
|
|
293
|
|
|
return $jsonLd; |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
/** |
297
|
|
|
* @param Calendar $otherCalendar |
298
|
|
|
* @return bool |
299
|
|
|
*/ |
300
|
|
|
public function sameAs(Calendar $otherCalendar) |
301
|
|
|
{ |
302
|
|
|
return $this->toJsonLd() == $otherCalendar->toJsonLd(); |
303
|
|
|
} |
304
|
|
|
|
305
|
|
|
/** |
306
|
|
|
* @param Udb3ModelCalendar $calendar |
307
|
|
|
* @return self |
308
|
|
|
*/ |
309
|
|
|
public static function fromUdb3ModelCalendar(Udb3ModelCalendar $calendar) |
310
|
|
|
{ |
311
|
|
|
$type = CalendarType::fromNative($calendar->getType()->toString()); |
312
|
|
|
|
313
|
|
|
$startDate = null; |
314
|
|
|
$endDate = null; |
315
|
|
|
$timestamps = []; |
316
|
|
|
$openingHours = []; |
317
|
|
|
|
318
|
|
|
if ($calendar instanceof CalendarWithDateRange) { |
319
|
|
|
$startDate = $calendar->getStartDate(); |
320
|
|
|
$endDate = $calendar->getEndDate(); |
321
|
|
|
} |
322
|
|
|
|
323
|
|
|
if ($calendar instanceof CalendarWithSubEvents) { |
324
|
|
|
$timestamps = array_map( |
325
|
|
|
function (DateRange $dateRange) { |
326
|
|
|
return Timestamp::fromUdb3ModelDateRange($dateRange); |
327
|
|
|
}, |
328
|
|
|
$calendar->getSubEvents()->toArray() |
329
|
|
|
); |
330
|
|
|
} |
331
|
|
|
|
332
|
|
|
if ($calendar instanceof CalendarWithOpeningHours) { |
333
|
|
|
$openingHours = array_map( |
334
|
|
|
function (Udb3ModelOpeningHour $openingHour) { |
335
|
|
|
return OpeningHour::fromUdb3ModelOpeningHour($openingHour); |
336
|
|
|
}, |
337
|
|
|
$calendar->getOpeningHours()->toArray() |
338
|
|
|
); |
339
|
|
|
} |
340
|
|
|
|
341
|
|
|
return new self($type, $startDate, $endDate, $timestamps, $openingHours); |
342
|
|
|
} |
343
|
|
|
} |
344
|
|
|
|
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.