Lines 95.18% 277 / 291
Methods 85.45% 47 / 55
Classes 0.00% 0 / 1
Name Lines Methods CRAP
 getDefaults 100.00% 9 / 9 100.00% 1 / 1 1
 addDates 100.00% 5 / 5 100.00% 1 / 1 3
 addFirstAndLastDay 100.00% 15 / 15 100.00% 1 / 1 1
 addProvider 100.00% 7 / 7 100.00% 1 / 1 3
 addCluster 100.00% 5 / 5 100.00% 1 / 1 2
 addRequest 100.00% 7 / 7 100.00% 1 / 1 3
 addScope 100.00% 6 / 6 100.00% 1 / 1 3
 getScopeList 100.00% 6 / 6 100.00% 1 / 1 3
 getRequestList 100.00% 5 / 5 100.00% 1 / 1 2
 getProviderList 100.00% 5 / 5 100.00% 1 / 1 2
 getDayList 100.00% 5 / 5 100.00% 1 / 1 2
 hasDay 100.00% 1 / 1 100.00% 1 / 1 1
 getDay 100.00% 1 / 1 100.00% 1 / 1 1
 getDayByDateTime 100.00% 1 / 1 100.00% 1 / 1 1
 getDateTimeFromDate 100.00% 3 / 3 100.00% 1 / 1 2
 hasFirstAndLastDay 100.00% 5 / 5 100.00% 1 / 1 3
 getFirstDay 100.00% 10 / 10 100.00% 1 / 1 2
 getLastDay 91.66% 11 / 12 0.00% 0 / 1 4.01
 setLastDayTime 100.00% 4 / 4 100.00% 1 / 1 1
 setFirstDayTime 100.00% 4 / 4 100.00% 1 / 1 1
 getMonthList 100.00% 11 / 11 100.00% 1 / 1 2
 withLessData 100.00% 16 / 16 100.00% 1 / 1 5
 withFilledEmptyDays 100.00% 22 / 22 100.00% 1 / 1 4
 __toString 100.00% 8 / 8 100.00% 1 / 1 5
 [BO\Zmsentities\Schema\Entity] __construct 100.00% 4 / 4 100.00% 1 / 1 2
 [BO\Zmsentities\Schema\Entity] exchangeArray 100.00% 4 / 4 100.00% 1 / 1 1
 [BO\Zmsentities\Schema\Entity] offsetSet 100.00% 1 / 1 100.00% 1 / 1 1
 [BO\Zmsentities\Schema\Entity] getUnflattenedArray 100.00% 5 / 5 100.00% 1 / 1 2
 [BO\Zmsentities\Schema\Entity] getValidator 100.00% 6 / 6 100.00% 1 / 1 2
 [BO\Zmsentities\Schema\Entity] isValid 100.00% 2 / 2 100.00% 1 / 1 1
 [BO\Zmsentities\Schema\Entity] testValid 100.00% 7 / 7 100.00% 1 / 1 2
 [BO\Zmsentities\Schema\Entity] createExample 100.00% 2 / 2 100.00% 1 / 1 1
 [BO\Zmsentities\Schema\Entity] getExample 100.00% 5 / 5 100.00% 1 / 1 2
 [BO\Zmsentities\Schema\Entity] readJsonSchema 66.66% 6 / 9 0.00% 0 / 1 3.33
 [BO\Zmsentities\Schema\Entity] getEntityName 100.00% 4 / 4 100.00% 1 / 1 1
 [BO\Zmsentities\Schema\Entity] setJsonCompressLevel 100.00% 2 / 2 100.00% 1 / 1 1
 [BO\Zmsentities\Schema\Entity] jsonSerialize 100.00% 11 / 11 100.00% 1 / 1 2
 [BO\Zmsentities\Schema\Entity] __get 0.00% 0 / 1 0.00% 0 / 1 2
 [BO\Zmsentities\Schema\Entity] __isset 0.00% 0 / 1 0.00% 0 / 1 2
 [BO\Zmsentities\Schema\Entity] __set 0.00% 0 / 1 0.00% 0 / 1 2
 [BO\Zmsentities\Schema\Entity] __unset 0.00% 0 / 1 0.00% 0 / 1 2
 [BO\Zmsentities\Schema\Entity] __clone 100.00% 3 / 3 100.00% 1 / 1 3
 [BO\Zmsentities\Schema\Entity] addData 100.00% 14 / 14 100.00% 1 / 1 6
 [BO\Zmsentities\Schema\Entity] withData 0.00% 0 / 3 0.00% 0 / 1 2
 [BO\Zmsentities\Schema\Entity] hasId 100.00% 1 / 1 100.00% 1 / 1 2
 [BO\Zmsentities\Schema\Entity] getId 100.00% 2 / 2 100.00% 1 / 1 3
 [BO\Zmsentities\Schema\Entity] toProperty 100.00% 1 / 1 100.00% 1 / 1 1
 [BO\Zmsentities\Schema\Entity] hasProperty 100.00% 1 / 1 100.00% 1 / 1 1
 [BO\Zmsentities\Schema\Entity] getProperty 100.00% 1 / 1 100.00% 1 / 1 1
 [BO\Zmsentities\Schema\Entity] withProperty 0.00% 0 / 3 0.00% 0 / 1 2
 [BO\Zmsentities\Schema\Entity] withCleanedUpFormData 100.00% 6 / 6 100.00% 1 / 1 3
 [BO\Zmsentities\Schema\Entity] getResolveLevel 100.00% 1 / 1 100.00% 1 / 1 1
 [BO\Zmsentities\Schema\Entity] setResolveLevel 100.00% 2 / 2 100.00% 1 / 1 1
 [BO\Zmsentities\Schema\Entity] withResolveLevel 100.00% 9 / 9 100.00% 1 / 1 5
 [BO\Zmsentities\Schema\Entity] withReference 100.00% 5 / 5 100.00% 1 / 1 2
11class Calendar extends Schema\Entity
12{
13    public const string PRIMARY = 'days';
14
15    public static $schema = "calendar.json";
16
17    /**
18     * @return (Collection\DayList|Day|array|null)[]
19     *
20     */
21    #[\Override]
22    public function getDefaults()
23    {
24        return [
25            'firstDay' => new Day(),
26            'lastDay' => null,
27            'days' => new Collection\DayList(),
28            'clusters' => [ ],
29            'providers' => [ ],
30            'scopes' => [ ],
31            'requests' => [ ]
32        ];
33    }
34
35    public function addDates(mixed $date, \DateTimeInterface $now, mixed $timeZone): static
36    {
37        $validDate = \BO\Mellon\Validator::value($date)->isDate();
38        $date = (! $validDate->hasFailed()) ? $validDate->getValue() : $now->format('U');
39        if (! $this->toProperty()->firstDay->day->get()) {
40            $this->addFirstAndLastDay($date, $timeZone);
41        }
42        return $this;
43    }
44
45    /**
46     * Returns calendar with first and last day
47     *
48     * @return $this
49     */
50    public function addFirstAndLastDay(mixed $date, mixed $timeZone)
51    {
52        $timeZone = new \DateTimeZone($timeZone);
53        $dateTime = Helper\DateTime::create()->setTimezone($timeZone)->setTimestamp($date);
54        $firstDay = $dateTime->setTime(0, 0, 0);
55        $lastDay = $dateTime->modify('last day of next month')->setTime(23, 59, 59);
56        $this->firstDay = array(
57            'year' => $firstDay->format('Y'),
58            'month' => $firstDay->format('m'),
59            'day' => $firstDay->format('d')
60        );
61        $this->lastDay = array(
62            'year' => $lastDay->format('Y'),
63            'month' => $lastDay->format('m'),
64            'day' => $lastDay->format('d')
65        );
66        return $this;
67    }
68
69    /**
70     * Returns calendar with added Providers
71     *
72     * @return $this
73     */
74    public function addProvider(mixed $source, mixed $idList)
75    {
76        foreach (explode(',', $idList) as $id) {
77            if ($id) {
78                $provider = new Provider();
79                $provider->source = $source;
80                $provider->id = $id;
81                $this->providers[] = $provider;
82            }
83        }
84        return $this;
85    }
86
87    /**
88     * Returns calendar with added Clusters
89     *
90     * @return $this
91     */
92    public function addCluster(mixed $idList)
93    {
94        foreach (explode(',', $idList) as $id) {
95            $cluster = new Cluster();
96            $cluster->id = $id;
97            $this->clusters[] = $cluster;
98        }
99        return $this;
100    }
101
102    /**
103     * Returns calendar with added requests
104     *
105     * @return $this
106     */
107    public function addRequest(mixed $source, mixed $requestList)
108    {
109        foreach (explode(',', $requestList) as $id) {
110            if ($id) {
111                $request = new Request();
112                $request->source = $source;
113                $request->id = $id;
114                $this->requests[] = $request;
115            }
116        }
117        return $this;
118    }
119
120    /**
121     * Returns calendar with added scopes
122     *
123     * @return $this
124     */
125    public function addScope(mixed $scopeList)
126    {
127        foreach (explode(',', $scopeList) as $id) {
128            if ($id) {
129                $scope = new Scope();
130                $scope->id = $id;
131                $this->scopes[] = $scope;
132            }
133        }
134        return $this;
135    }
136
137    /**
138     * Returns a list of associated scope ids
139     *
140     * @return Collection\ScopeList
141     */
142    public function getScopeList(): Collection\ScopeList
143    {
144        $scopeList = new \BO\Zmsentities\Collection\ScopeList();
145        if (isset($this->scopes)) {
146            foreach ($this->scopes as $scope) {
147                $scope = new Scope($scope);
148                $scopeList->addEntity($scope);
149            }
150        }
151        return $scopeList;
152    }
153
154    /**
155     * Returns a list of associated request entities
156     *
157     * @return Collection\RequestList
158     */
159    public function getRequestList(): Collection\RequestList
160    {
161        $requestList = new \BO\Zmsentities\Collection\RequestList();
162        foreach ($this->requests as $request) {
163            $request = new Request($request);
164            $requestList->addEntity($request);
165        }
166        return $requestList;
167    }
168
169    /**
170     * Returns a list of associated provider ids
171     *
172     * @return Collection\ProviderList
173     */
174    public function getProviderList(): Collection\ProviderList
175    {
176        $providerList = new \BO\Zmsentities\Collection\ProviderList();
177        foreach ($this->providers as $provider) {
178            $entity = new Provider($provider);
179            $providerList->addEntity($entity);
180        }
181        return $providerList;
182    }
183
184    public function getDayList(): Collection\DayList
185    {
186        $days = $this->days;
187        if (!$days instanceof Collection\DayList) {
188            $days = new Collection\DayList($days);
189            $this->days = $days;
190        }
191        return $days->setSortByDate();
192    }
193
194    /**
195     * Check if given day exists in calendar
196     *
197     * @return bool
198     */
199    public function hasDay(mixed $year, mixed $month, mixed $dayNumber)
200    {
201        return $this->getDayList()->hasDay($year, $month, $dayNumber);
202    }
203
204    /**
205     * Returns a day by given year, month and daynumber
206     *
207     * @return \ArrayObject
208     */
209    public function getDay(mixed $year, mixed $month, mixed $dayNumber)
210    {
211        return $this->getDayList()->getDay($year, $month, $dayNumber);
212    }
213
214    public function getDayByDateTime(\DateTimeInterface $datetime): mixed
215    {
216        return $this->getDayList()->getDayByDateTime($datetime);
217    }
218
219    public function getDateTimeFromDate(array $date): \BO\Zmsentities\Helper\DateTime
220    {
221        $day = (isset($date['day'])) ? $date['day'] : 1;
222        $date = Helper\DateTime::createFromFormat('Y-m-d', $date['year'] . '-' . $date['month'] . '-' . $day);
223        return Helper\DateTime::create($date);
224    }
225
226    /**
227     * Simple quick check, if first and last day are defined
228     */
229    public function hasFirstAndLastDay(): bool
230    {
231        if (!$this->toProperty()->firstDay->day->get()) {
232            return false;
233        }
234        if (!$this->toProperty()->lastDay->day->get()) {
235            return false;
236        }
237        return true;
238    }
239
240    public function getFirstDay(): mixed
241    {
242        if (isset($this['firstDay'])) {
243            $dateTime = $this->getDateTimeFromDate(
244                array(
245                    'year' => $this['firstDay']['year'],
246                    'month' => $this['firstDay']['month'],
247                    'day' => $this['firstDay']['day']
248                )
249            );
250        } else {
251            $dateTime = Helper\DateTime::create();
252        }
253        return $dateTime->modify('00:00:00');
254    }
255
256    public function getLastDay(bool $createIfNotProvided = true): mixed
257    {
258        if (! $createIfNotProvided && ! isset($this['lastDay'])) {
259            return null;
260        }
261
262        if (isset($this['lastDay'])) {
263            $dateTime = $this->getDateTimeFromDate(
264                array(
265                    'year' => $this['lastDay']['year'],
266                    'month' => $this['lastDay']['month'],
267                    'day' => $this['lastDay']['day']
268                )
269            );
270        } else {
271            $dateTime = Helper\DateTime::create();
272        }
273        return $dateTime->modify('23:59:59');
274    }
275
276    public function setLastDayTime(mixed $date): static
277    {
278        $day = new Day();
279        $day->setDateTime($date);
280        $this['lastDay'] = $day;
281        return $this;
282    }
283
284    public function setFirstDayTime(mixed $date): static
285    {
286        $day = new Day();
287        $day->setDateTime($date);
288        $this['firstDay'] = $day;
289        return $this;
290    }
291
292    /**
293     * Returns a list of contained month given by firstDay and lastDay
294     * The return value is a month entity object for the first day of the month
295     */
296    public function getMonthList(): Collection\MonthList
297    {
298        $firstDay = Helper\DateTime::create($this->getFirstDay())->modify('first day of this month')->modify('00:00:00');
299        $lastDay = Helper\DateTime::create($this->getLastDay())->modify('last day of this month')->modify('23:59:59');
300        $currentDate = $firstDay;
301        if ($firstDay->getTimestamp() > $lastDay->getTimestamp()) {
302            // switch first and last day if necessary
303            $currentDate = $lastDay;
304            $lastDay = $firstDay;
305        }
306        $monthList = new Collection\MonthList();
307        do {
308            $monthList->addEntity(Month::createForDateFromDayList($currentDate, $this->days));
309            $currentDate = $currentDate->modify('+1 month');
310        } while ($currentDate->getTimestamp() < $lastDay->getTimestamp());
311        return $monthList;
312    }
313
314    /**
315     * Reduce data of dereferenced entities to a required minimum
316     *
317     * @return static
318     */
319    #[\Override]
320    public function withLessData()
321    {
322        $entity = clone $this;
323
324        foreach ($entity['scopes'] as $scope) {
325            if ($scope->toProperty()->provider->data->isAvailable()) {
326                $payment = $scope->toProperty()->provider->data->payment->get();
327                unset($scope['provider']['data']);
328                $scope['provider']['data'] = ['payment' => $payment];
329                unset($scope['dayoff']);
330                unset($scope['status']);
331                unset($scope['preferences']);
332            }
333        }
334        foreach ($entity['days'] as $day) {
335            if (isset($day['allAppointments'])) {
336                unset($day['allAppointments']);
337            }
338        }
339        unset($entity['providers']);
340        unset($entity['clusters']);
341        unset($entity['freeProcesses']);
342        return $entity;
343    }
344
345    public function withFilledEmptyDays(): static
346    {
347        $entity = clone $this;
348
349        $firstDay = Helper\DateTime::create($this->getFirstDay())->modify('first day of this month')->modify('00:00:00');
350        $lastDay = Helper\DateTime::create($this->getLastDay())->modify('last day of this month')->modify('23:59:59');
351        $currentDate = $firstDay;
352        $dayList = new Collection\DayList($entity->days);
353
354        do {
355            $day = new Day([
356                'year' => $currentDate->format('Y'),
357                'month' => $currentDate->format('m'),
358                'day' => $currentDate->format('d')
359            ]);
360            $dayTimestamp = $day->toDateTime()->getTimestamp();
361            $dayFound = false;
362
363            foreach ($dayList as $checkingDay) {
364                $checkingTimestamp = $checkingDay->toDateTime()->getTimestamp();
365                if ($checkingTimestamp === $dayTimestamp) {
366                    $dayFound = true;
367                }
368            }
369
370            if (!$dayFound) {
371                $dayList->addEntity($day);
372            }
373
374            $currentDate = $currentDate->modify('+1 day');
375        } while ($currentDate->getTimestamp() < $lastDay->getTimestamp());
376
377        $entity->days = $dayList;
378
379        return $entity;
380    }
381
382    public function __toString()
383    {
384        $string = '';
385        foreach ($this->days as $day) {
386            $day = ($day instanceof Day) ? $day : new Day($day);
387            $string .= "$day\n";
388        }
389        foreach ($this->scopes as $scope) {
390            $scope = ($scope instanceof Scope) ? $scope : new Scope($scope);
391            $string .= "$scope\n";
392        }
393        return $string;
394    }
395}

Inherited from BO\Zmsentities\Schema\Entity

59    public function __construct(mixed $input = null, int $flags = \ArrayObject::ARRAY_AS_PROPS, string $iterator_class = "ArrayIterator")
60    {
61        /** @var class-string<\ArrayIterator> $iterator_class */
62        parent::__construct($this->getDefaults(), $flags, $iterator_class);
63        if ($input) {
64            $input = $this->getUnflattenedArray($input);
65            $this->addData($input);
66        }
67    }
70    public function exchangeArray(object|array $input): array
71    {
72        parent::exchangeArray($this->getDefaults());
73        $input = $this->getUnflattenedArray($input);
74        $this->addData($input);
75        return $this->getArrayCopy();
76    }
82    public function offsetSet(mixed $key, mixed $value): void
83    {
84        /** @psalm-suppress PossiblyNullArgument */
85        parent::offsetSet($key, $value);
86    }
88    public function getUnflattenedArray(mixed $input): mixed
89    {
90        if (!$input instanceof UnflattedArray) {
91            $input = new UnflattedArray($input);
92            $input->getUnflattenedArray();
93        }
94        $input = $input->getValue();
95        return $input;
96    }
112    public function getValidator(string $locale = 'de_DE', int $resolveLevel = 0): Validator
113    {
114        $jsonSchema = self::readJsonSchema()->withResolvedReferences($resolveLevel);
115        $data = (new Schema($this))->withoutRefs();
116        if (Property::__keyExists('$schema', $data)) {
117            unset($data['$schema']);
118        }
119        $validator = new Validator($data->toJsonObject(true), $jsonSchema, $locale);
120        return $validator;
121    }
128    public function isValid(int $resolveLevel = 0): bool
129    {
130        $validator = $this->getValidator('de_DE', $resolveLevel = 0);
131        return $validator->isValid();
132    }
140    public function testValid(string $locale = 'de_DE', int $resolveLevel = 0): bool
141    {
142        $validator = $this->getValidator($locale, $resolveLevel);
143        if (!$validator->isValid()) {
144            $exception = new \BO\Zmsentities\Exception\SchemaValidation();
145            $exception->setSchemaName($this->getEntityName());
146            $exception->setValidationError($validator->getErrors());
147            throw $exception;
148        }
149        return true;
150    }
157    public static function createExample()
158    {
159        $object = new static();
160        return $object->getExample();
161    }
168    public static function getExample()
169    {
170        $class = get_called_class();
171        $jsonSchema = self::readJsonSchema();
172        if ($jsonSchema->offsetExists('example')) {
173            return new $class($jsonSchema['example']);
174        }
175        return new $class();
176    }
178    protected static function readJsonSchema(): mixed
179    {
180        $class = get_called_class();
181        if (!array_key_exists($class, self::$schemaCache)) {
182            $schemaFile = $class::$schema;
183            if (!is_string($schemaFile)) {
184                throw new \BO\Zmsentities\Exception\SchemaMissingJsonFile(
185                    'Missing JSON-Schema file for ' . $class
186                );
187            }
188            self::$schemaCache[$class] = Loader::asArray($schemaFile);
189        }
190        return self::$schemaCache[$class];
191    }
193    public function getEntityName(): string
194    {
195        $entity = get_class($this);
196        $entity = preg_replace('#.*[\\\]#', '', $entity) ?? '';
197        $entity = strtolower($entity);
198        return $entity;
199    }
201    public function setJsonCompressLevel(mixed $jsonCompressLevel): static
202    {
203        $this->jsonCompressLevel = $jsonCompressLevel;
204        return $this;
205    }
208    public function jsonSerialize(): mixed
209    {
210        $schema = array(
211            '$schema' => 'https://schema.berlin.de/queuemanagement/' . $this->getEntityName() . '.json'
212        );
213        $schema = array_merge($schema, $this->getArrayCopy());
214        if ($this instanceof \BO\Zmsentities\Helper\NoSanitize) {
215            $serialize = $schema;
216        } else {
217            $schema = new Schema($schema);
218            $schema->setDefaults($this->getDefaults());
219            $schema->setJsonCompressLevel($this->jsonCompressLevel);
220            $serialize = $schema->toJsonObject();
221        }
222        return $serialize;
223    }
235    public function __get(string $name): mixed
236    {
237        return $this->offsetGet($name);
238    }
240    public function __isset(string $name): bool
241    {
242        return $this->offsetExists($name);
243    }
245    public function __set(string $name, mixed $value): void
246    {
247        $this->offsetSet($name, $value);
248    }
250    public function __unset(string $name): void
251    {
252        $this->offsetUnset($name);
253    }
255    public function __clone()
256    {
257        foreach ($this as $key => $property) {
258            if (is_object($property)) {
259                $this[$key] = clone $property;
260            }
261        }
262    }
268    public function addData(array|object $mergeData): static
269    {
270        foreach ($mergeData as $key => $item) {
271            if (isset($this[$key])) {
272                if ($this[$key] instanceof Entity) {
273                    $this[$key]->setResolveLevel(($this->getResolveLevel() ?? 0) - 1);
274                    $this[$key]->addData($item);
275                } elseif ($this[$key] instanceof \BO\Zmsentities\Collection\Base) {
276                    $this[$key]->exchangeArray([]);
277                    $this[$key]->setResolveLevel(($this->getResolveLevel() ?? 0) - 1);
278                    $this[$key]->addData($item);
279                } elseif (is_array($this[$key])) {
280                    $this[$key] = array_replace_recursive($this[$key], $item);
281                } else {
282                    $this[$key] = $item;
283                }
284            } else {
285                $this[$key] = $item;
286            }
287        }
288        return $this;
289    }
294    public function withData(mixed $mergeData): static
295    {
296        $entity = clone $this;
297        $entity->addData($mergeData);
298        return $entity;
299    }
301    public function hasId(): bool
302    {
303        return (false !== $this->getId()) ? true : false;
304    }
306    public function getId(): mixed
307    {
308        $idName = $this::PRIMARY;
309        return ($this->offsetExists($idName) && $this[$idName]) ? $this[$idName] : false;
310    }
317    public function toProperty()
318    {
319        return new \BO\Zmsentities\Helper\Property($this);
320    }
322    public function hasProperty(mixed $propertyName): mixed
323    {
324        return $this->toProperty()->{$propertyName}->isAvailable();
325    }
327    public function getProperty(string $propertyName, mixed $default = ''): mixed
328    {
329        return $this->toProperty()->{$propertyName}->get($default);
330    }
335    public function withProperty(mixed $propertyName, mixed $newValue): static
336    {
337        $entity = clone $this;
338        $entity[$propertyName] = $newValue;
339        return $entity;
340    }
357    public function withCleanedUpFormData()
358    {
359        $entity = clone $this;
360        if (isset($entity['save'])) {
361            unset($entity['save']);
362        }
363        if (isset($entity['removeImage'])) {
364            unset($entity['removeImage']);
365        }
366        return $entity;
367    }
372    public function getResolveLevel()
373    {
374        return $this->resolveLevel;
375    }
381    public function setResolveLevel($resolveLevel)
382    {
383        $this->resolveLevel = $resolveLevel;
384        return $this;
385    }
393    public function withResolveLevel($resolveLevel): self|array
394    {
395        if ($resolveLevel >= 0) {
396            $entity = clone $this;
397            foreach ($entity as $key => $value) {
398                if ($value instanceof Entity || $value instanceof \BO\Zmsentities\Collection\Base) {
399                    $entity[$key] = $value->withResolveLevel($resolveLevel - 1);
400                } else {
401                    $entity[$key] = $value;
402                }
403            }
404            $entity->setResolveLevel($resolveLevel);
405            return $entity;
406        } else {
407            return $this->withReference();
408        }
409    }
417    public function withReference($additionalData = []): self|array
418    {
419        if (isset($this[$this::PRIMARY])) {
420            $additionalData['$ref'] =
421                $this::$schemaRefPrefix . $this->getEntityName() . '/' . $this[$this::PRIMARY] . '/';
422            return $additionalData;
423        } else {
424            return $this->withResolveLevel(0);
425        }
426    }