GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( a40f7e...ee95e0 )
by Kyle
01:20
created

OpeningHours::getDayLimit()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
1
<?php
2
3
namespace Spatie\OpeningHours;
4
5
use DateTime;
6
use DateTimeZone;
7
use DateTimeImmutable;
8
use DateTimeInterface;
9
use Spatie\OpeningHours\Exceptions\MaximumLimitExceeded;
10
use Spatie\OpeningHours\Helpers\Arr;
11
use Spatie\OpeningHours\Helpers\DataTrait;
12
use Spatie\OpeningHours\Exceptions\Exception;
13
use Spatie\OpeningHours\Exceptions\InvalidDate;
14
use Spatie\OpeningHours\Exceptions\InvalidDayName;
15
16
class OpeningHours
17
{
18
    const DEFAULT_DAY_LIMIT = 8;
19
20
    use DataTrait;
21
22
    /** @var \Spatie\OpeningHours\Day[] */
23
    protected $openingHours = [];
24
25
    /** @var \Spatie\OpeningHours\OpeningHoursForDay[] */
26
    protected $exceptions = [];
27
28
    /** @var callable[] */
29
    protected $filters = [];
30
31
    /** @var DateTimeZone|null */
32
    protected $timezone = null;
33
34
    /** @var bool Allow for overflowing time ranges which overflow into the next day */
35
    protected $overflow;
36
37
    /** @var int Number of days to try before abandoning the search of the next close/open time */
38
    protected $dayLimit = null;
39
40
    public function __construct($timezone = null)
41
    {
42
        if ($timezone instanceof DateTimeZone) {
43
            $this->timezone = $timezone;
44
        } elseif (is_string($timezone)) {
45
            $this->timezone = new DateTimeZone($timezone);
46
        } elseif ($timezone) {
47
            throw new \InvalidArgumentException('Invalid Timezone');
48
        }
49
50
        $this->openingHours = Day::mapDays(function () {
51
            return new OpeningHoursForDay();
52
        });
53
    }
54
55
    /**
56
     * @param string[][]               $data
57
     * @param string|DateTimeZone|null $timezone
58
     *
59
     * @return static
60
     */
61
    public static function create(array $data, $timezone = null): self
62
    {
63
        return (new static($timezone))->fill($data);
64
    }
65
66
    /**
67
     * @param array $data
68
     *
69
     * @return array
70
     */
71
    public static function mergeOverlappingRanges(array $data)
72
    {
73
        $result = [];
74
        $ranges = [];
75
        foreach ($data as $key => $value) {
76
            $value = is_array($value)
77
                ? static::mergeOverlappingRanges($value)
78
                : (is_string($value) ? TimeRange::fromString($value) : $value);
79
80
            if ($value instanceof TimeRange) {
81
                $newRanges = [];
82
                foreach ($ranges as $range) {
83
                    if ($value->format() === $range->format()) {
84
                        continue 2;
85
                    }
86
87
                    if ($value->overlaps($range) || $range->overlaps($value)) {
88
                        $value = TimeRange::fromList([$value, $range]);
89
90
                        continue;
91
                    }
92
93
                    $newRanges[] = $range;
94
                }
95
96
                $newRanges[] = $value;
97
                $ranges = $newRanges;
98
99
                continue;
100
            }
101
102
            $result[$key] = $value;
103
        }
104
105
        foreach ($ranges as $range) {
106
            $result[] = $range;
107
        }
108
109
        return $result;
110
    }
111
112
    /**
113
     * @param string[][]               $data
114
     * @param string|DateTimeZone|null $timezone
115
     *
116
     * @return static
117
     */
118
    public static function createAndMergeOverlappingRanges(array $data, $timezone = null)
119
    {
120
        return static::create(static::mergeOverlappingRanges($data), $timezone);
121
    }
122
123
    /**
124
     * @param array $data
125
     *
126
     * @return bool
127
     */
128
    public static function isValid(array $data): bool
129
    {
130
        try {
131
            static::create($data);
132
133
            return true;
134
        } catch (Exception $exception) {
135
            return false;
136
        }
137
    }
138
139
    /**
140
     * Set the number of days to try before abandoning the search of the next close/open time.
141
     *
142
     * @param int $dayLimit number of days
143
     */
144
    public function setDayLimit(int $dayLimit)
145
    {
146
        $this->dayLimit = $dayLimit;
147
    }
148
149
    /**
150
     * Get the number of days to try before abandoning the search of the next close/open time.
151
     *
152
     * @return int
153
     */
154
    public function getDayLimit(): int
155
    {
156
        return $this->dayLimit ?: static::DEFAULT_DAY_LIMIT;
157
    }
158
159
    public function setFilters(array $filters)
160
    {
161
        $this->filters = $filters;
162
163
        return $this;
164
    }
165
166
    public function getFilters(): array
167
    {
168
        return $this->filters;
169
    }
170
171
    public function fill(array $data)
172
    {
173
        list($openingHours, $exceptions, $metaData, $filters, $overflow) = $this->parseOpeningHoursAndExceptions($data);
174
175
        $this->overflow = $overflow;
176
177
        foreach ($openingHours as $day => $openingHoursForThisDay) {
178
            $this->setOpeningHoursFromStrings($day, $openingHoursForThisDay);
179
        }
180
181
        $this->setExceptionsFromStrings($exceptions);
182
183
        return $this->setFilters($filters)->setData($metaData);
184
    }
185
186
    public function forWeek(): array
187
    {
188
        return $this->openingHours;
189
    }
190
191
    public function forWeekCombined(): array
192
    {
193
        $equalDays = [];
194
        $allOpeningHours = $this->openingHours;
195
        $uniqueOpeningHours = array_unique($allOpeningHours);
196
        $nonUniqueOpeningHours = $allOpeningHours;
197
198
        foreach ($uniqueOpeningHours as $day => $value) {
199
            $equalDays[$day] = ['days' => [$day], 'opening_hours' => $value];
200
            unset($nonUniqueOpeningHours[$day]);
201
        }
202
203
        foreach ($uniqueOpeningHours as $uniqueDay => $uniqueValue) {
204
            foreach ($nonUniqueOpeningHours as $nonUniqueDay => $nonUniqueValue) {
205
                if ((string) $uniqueValue === (string) $nonUniqueValue) {
206
                    $equalDays[$uniqueDay]['days'][] = $nonUniqueDay;
207
                }
208
            }
209
        }
210
211
        return $equalDays;
212
    }
213
214
    public function forDay(string $day): OpeningHoursForDay
215
    {
216
        $day = $this->normalizeDayName($day);
217
218
        return $this->openingHours[$day];
219
    }
220
221
    public function forDate(DateTimeInterface $date): OpeningHoursForDay
222
    {
223
        $date = $this->applyTimezone($date);
224
225
        foreach ($this->filters as $filter) {
226
            $result = $filter($date);
227
228
            if (is_array($result)) {
229
                return OpeningHoursForDay::fromStrings($result);
230
            }
231
        }
232
233
        return $this->exceptions[$date->format('Y-m-d')] ?? ($this->exceptions[$date->format('m-d')] ?? $this->forDay(Day::onDateTime($date)));
234
    }
235
236
    public function exceptions(): array
237
    {
238
        return $this->exceptions;
239
    }
240
241
    public function isOpenOn(string $day): bool
242
    {
243
        return count($this->forDay($day)) > 0;
244
    }
245
246
    public function isClosedOn(string $day): bool
247
    {
248
        return ! $this->isOpenOn($day);
249
    }
250
251
    public function isOpenAt(DateTimeInterface $dateTime): bool
252
    {
253
        $dateTime = $this->applyTimezone($dateTime);
254
255
        if ($this->overflow) {
256
            $yesterdayDateTime = $dateTime;
257
            if (! ($yesterdayDateTime instanceof DateTimeImmutable)) {
258
                $yesterdayDateTime = clone $yesterdayDateTime;
259
            }
260
            $dateTimeMinus1Day = $yesterdayDateTime->sub(new \DateInterval('P1D'));
261
            $openingHoursForDayBefore = $this->forDate($dateTimeMinus1Day);
262
            if ($openingHoursForDayBefore->isOpenAtNight(Time::fromDateTime($dateTimeMinus1Day))) {
263
                return true;
264
            }
265
        }
266
267
        $openingHoursForDay = $this->forDate($dateTime);
268
269
        return $openingHoursForDay->isOpenAt(Time::fromDateTime($dateTime));
270
    }
271
272
    public function isClosedAt(DateTimeInterface $dateTime): bool
273
    {
274
        return ! $this->isOpenAt($dateTime);
275
    }
276
277
    public function isOpen(): bool
278
    {
279
        return $this->isOpenAt(new DateTime());
280
    }
281
282
    public function isClosed(): bool
283
    {
284
        return $this->isClosedAt(new DateTime());
285
    }
286
287
    public function nextOpen(DateTimeInterface $dateTime): DateTimeInterface
288
    {
289
        if (! ($dateTime instanceof DateTimeImmutable)) {
290
            $dateTime = clone $dateTime;
291
        }
292
293
        $openingHoursForDay = $this->forDate($dateTime);
294
        $nextOpen = $openingHoursForDay->nextOpen(Time::fromDateTime($dateTime));
295
        $tries = $this->getDayLimit();
296
297 View Code Duplication
        while ($nextOpen === false || $nextOpen->hours() >= 24) {
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...
298
            if (--$tries < 0) {
299
                throw MaximumLimitExceeded::forString(
300
                    'No open date/time found in the next '.$this->getDayLimit().' days,'.
301
                    ' use $openingHours->setDayLimit() to increase the limit.'
302
                );
303
            }
304
305
            $dateTime = $dateTime
306
                ->modify('+1 day')
307
                ->setTime(0, 0, 0);
308
309
            if ($this->isOpenAt($dateTime) && ! $openingHoursForDay->isOpenAt(Time::fromString('23:59'))) {
310
                return $dateTime;
311
            }
312
313
            $openingHoursForDay = $this->forDate($dateTime);
314
315
            $nextOpen = $openingHoursForDay->nextOpen(Time::fromDateTime($dateTime));
316
        }
317
318
        if ($dateTime->format('H:i') === '00:00' && $this->isOpenAt((clone $dateTime)->modify('-1 second'))) {
319
            return $this->nextOpen($dateTime->modify('+1 minute'));
320
        }
321
322
        $nextDateTime = $nextOpen->toDateTime();
323
        $dateTime = $dateTime->setTime($nextDateTime->format('G'), $nextDateTime->format('i'), 0);
324
325
        return $dateTime;
326
    }
327
328
    public function nextClose(DateTimeInterface $dateTime): DateTimeInterface
329
    {
330
        if (! ($dateTime instanceof DateTimeImmutable)) {
331
            $dateTime = clone $dateTime;
332
        }
333
334
        $nextClose = null;
335
        if ($this->overflow) {
336
            $yesterday = $dateTime;
337
            if (! ($dateTime instanceof DateTimeImmutable)) {
338
                $yesterday = clone $dateTime;
339
            }
340
            $dateTimeMinus1Day = $yesterday->sub(new \DateInterval('P1D'));
341
            $openingHoursForDayBefore = $this->forDate($dateTimeMinus1Day);
342
            if ($openingHoursForDayBefore->isOpenAtNight(Time::fromDateTime($dateTimeMinus1Day))) {
343
                $nextClose = $openingHoursForDayBefore->nextClose(Time::fromDateTime($dateTime));
344
            }
345
        }
346
347
        $openingHoursForDay = $this->forDate($dateTime);
348
        if (! $nextClose) {
349
            $nextClose = $openingHoursForDay->nextClose(Time::fromDateTime($dateTime));
350
        }
351
352
        $tries = $this->getDayLimit();
353
354 View Code Duplication
        while ($nextClose === false || $nextClose->hours() >= 24) {
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...
355
            if (--$tries < 0) {
356
                throw MaximumLimitExceeded::forString(
357
                    'No close date/time found in the next '.$this->getDayLimit().' days,'.
358
                    ' use $openingHours->setDayLimit() to increase the limit.'
359
                );
360
            }
361
362
            $dateTime = $dateTime
363
                ->modify('+1 day')
364
                ->setTime(0, 0, 0);
365
366
            if ($this->isClosedAt($dateTime) && $openingHoursForDay->isOpenAt(Time::fromString('23:59'))) {
367
                return $dateTime;
368
            }
369
370
            $openingHoursForDay = $this->forDate($dateTime);
371
372
            $nextClose = $openingHoursForDay->nextClose(Time::fromDateTime($dateTime));
373
        }
374
375
        $nextDateTime = $nextClose->toDateTime();
376
        $dateTime = $dateTime->setTime($nextDateTime->format('G'), $nextDateTime->format('i'), 0);
377
378
        return $dateTime;
379
    }
380
381
    public function regularClosingDays(): array
382
    {
383
        return array_keys($this->filter(function (OpeningHoursForDay $openingHoursForDay) {
384
            return $openingHoursForDay->isEmpty();
385
        }));
386
    }
387
388
    public function regularClosingDaysISO(): array
389
    {
390
        return Arr::map($this->regularClosingDays(), [Day::class, 'toISO']);
391
    }
392
393
    public function exceptionalClosingDates(): array
394
    {
395
        $dates = array_keys($this->filterExceptions(function (OpeningHoursForDay $openingHoursForDay) {
396
            return $openingHoursForDay->isEmpty();
397
        }));
398
399
        return Arr::map($dates, function ($date) {
400
            return DateTime::createFromFormat('Y-m-d', $date);
401
        });
402
    }
403
404
    public function setTimezone($timezone)
405
    {
406
        $this->timezone = new DateTimeZone($timezone);
407
    }
408
409
    protected function parseOpeningHoursAndExceptions(array $data): array
410
    {
411
        $metaData = Arr::pull($data, 'data', null);
412
        $exceptions = [];
413
        $filters = Arr::pull($data, 'filters', []);
414
        $overflow = (bool) Arr::pull($data, 'overflow', false);
415
416
        foreach (Arr::pull($data, 'exceptions', []) as $key => $exception) {
417
            if (is_callable($exception)) {
418
                $filters[] = $exception;
419
420
                continue;
421
            }
422
423
            $exceptions[$key] = $exception;
424
        }
425
426
        $openingHours = [];
427
428
        foreach ($data as $day => $openingHoursData) {
429
            $openingHours[$this->normalizeDayName($day)] = $openingHoursData;
430
        }
431
432
        return [$openingHours, $exceptions, $metaData, $filters, $overflow];
433
    }
434
435
    protected function setOpeningHoursFromStrings(string $day, array $openingHours)
436
    {
437
        $day = $this->normalizeDayName($day);
438
439
        $data = null;
440
441
        if (isset($openingHours['data'])) {
442
            $data = $openingHours['data'];
443
            unset($openingHours['data']);
444
        }
445
446
        $this->openingHours[$day] = OpeningHoursForDay::fromStrings($openingHours)->setData($data);
447
    }
448
449
    protected function setExceptionsFromStrings(array $exceptions)
450
    {
451
        if (empty($exceptions)) {
452
            return;
453
        }
454
455
        if (!$this->dayLimit) {
456
            $this->dayLimit = 366;
457
        }
458
459
        $this->exceptions = Arr::map($exceptions, function (array $openingHours, string $date) {
460
            $recurring = DateTime::createFromFormat('m-d', $date);
461
462
            if ($recurring === false || $recurring->format('m-d') !== $date) {
463
                $dateTime = DateTime::createFromFormat('Y-m-d', $date);
464
465
                if ($dateTime === false || $dateTime->format('Y-m-d') !== $date) {
466
                    throw InvalidDate::invalidDate($date);
467
                }
468
            }
469
470
            return OpeningHoursForDay::fromStrings($openingHours);
471
        });
472
    }
473
474
    protected function normalizeDayName(string $day)
475
    {
476
        $day = strtolower($day);
477
478
        if (! Day::isValid($day)) {
479
            throw InvalidDayName::invalidDayName($day);
480
        }
481
482
        return $day;
483
    }
484
485
    protected function applyTimezone(DateTimeInterface $date)
486
    {
487
        if ($this->timezone) {
488
            $date = $date->setTimezone($this->timezone);
489
        }
490
491
        return $date;
492
    }
493
494
    public function filter(callable $callback): array
495
    {
496
        return Arr::filter($this->openingHours, $callback);
497
    }
498
499
    public function map(callable $callback): array
500
    {
501
        return Arr::map($this->openingHours, $callback);
502
    }
503
504
    public function flatMap(callable $callback): array
505
    {
506
        return Arr::flatMap($this->openingHours, $callback);
507
    }
508
509
    public function filterExceptions(callable $callback): array
510
    {
511
        return Arr::filter($this->exceptions, $callback);
512
    }
513
514
    public function mapExceptions(callable $callback): array
515
    {
516
        return Arr::map($this->exceptions, $callback);
517
    }
518
519
    public function flatMapExceptions(callable $callback): array
520
    {
521
        return Arr::flatMap($this->exceptions, $callback);
522
    }
523
524
    public function asStructuredData(): array
525
    {
526 View Code Duplication
        $regularHours = $this->flatMap(function (OpeningHoursForDay $openingHoursForDay, string $day) {
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...
527
            return $openingHoursForDay->map(function (TimeRange $timeRange) use ($day) {
528
                return [
529
                    '@type' => 'OpeningHoursSpecification',
530
                    'dayOfWeek' => ucfirst($day),
531
                    'opens' => (string) $timeRange->start(),
532
                    'closes' => (string) $timeRange->end(),
533
                ];
534
            });
535
        });
536
537
        $exceptions = $this->flatMapExceptions(function (OpeningHoursForDay $openingHoursForDay, string $date) {
538
            if ($openingHoursForDay->isEmpty()) {
539
                return [[
540
                    '@type' => 'OpeningHoursSpecification',
541
                    'opens' => '00:00',
542
                    'closes' => '00:00',
543
                    'validFrom' => $date,
544
                    'validThrough' => $date,
545
                ]];
546
            }
547
548 View Code Duplication
            return $openingHoursForDay->map(function (TimeRange $timeRange) use ($date) {
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...
549
                return [
550
                    '@type' => 'OpeningHoursSpecification',
551
                    'opens' => (string) $timeRange->start(),
552
                    'closes' => (string) $timeRange->end(),
553
                    'validFrom' => $date,
554
                    'validThrough' => $date,
555
                ];
556
            });
557
        });
558
559
        return array_merge($regularHours, $exceptions);
560
    }
561
}
562