Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
84.56% covered (warning)
84.56%
356 / 421
68.57% covered (warning)
68.57%
24 / 35
CRAP
0.00% covered (danger)
0.00%
0 / 1
Scope
84.56% covered (warning)
84.56%
356 / 421
68.57% covered (warning)
68.57%
24 / 35
224.45
0.00% covered (danger)
0.00%
0 / 1
 readEntity
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
8
 readEntitiesByIds
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
110
 readResolvedReferences
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 readByClusterId
100.00% covered (success)
100.00%
38 / 38
100.00% covered (success)
100.00%
1 / 1
12
 readByProviderId
78.12% covered (warning)
78.12%
25 / 32
0.00% covered (danger)
0.00%
0 / 1
12.27
 readByRequestId
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 readByDepartmentId
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
1 / 1
12
 readListBySource
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 testSource
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 readCollection
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 readList
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
10
 readIsOpened
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 readIsEnabled
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
3
 readWaitingNumberUpdated
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 readDisplayNumberUpdated
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 readIsGivenNumberInContingent
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 readQueueList
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
2
 readWithWorkstationCount
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
2
 readQueueListWithWaitingTime
66.67% covered (warning)
66.67%
6 / 9
0.00% covered (danger)
0.00%
0 / 1
3.33
 readScopesQueueListWithWaitingTime
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
3.01
 readListWithScopeAdminEmail
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
4
 writeEntity
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 updateEntity
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 replacePreferences
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 updateGhostWorkstationCount
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 updateEmergency
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 writeImageData
93.33% covered (success)
93.33%
14 / 15
0.00% covered (danger)
0.00%
0 / 1
4.00
 readImageData
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 deleteImage
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 deleteEntity
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
3
 deletePreferences
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 readDepartmentIdByScopeId
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 readClusterIdsByScopeId
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 removeCacheByContext
97.50% covered (success)
97.50%
39 / 40
0.00% covered (danger)
0.00%
0 / 1
22
 removeCache
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace BO\Zmsdb;
4
5use BO\Zmsentities\Scope as Entity;
6use BO\Zmsentities\Collection\ScopeList as Collection;
7
8/**
9 *
10 * @SuppressWarnings(Public)
11 * @SuppressWarnings(Coupling)
12 * @SuppressWarnings(Complexity)
13 * @SuppressWarnings(TooManyMethods)
14 *
15 */
16class Scope extends Base
17{
18    public static $cache = [ ];
19
20    public function readEntity($scopeId, $resolveReferences = 0, $disableCache = false)
21    {
22        $cacheKey = "scope-$scopeId-$resolveReferences";
23
24        if (!$disableCache && \App::$cache && \App::$cache->has($cacheKey)) {
25            $scope = \App::$cache->get($cacheKey);
26        }
27
28        if (empty($scope)) {
29            $query = new Query\Scope(Query\Base::SELECT);
30            $query->addEntityMapping()
31                ->addResolvedReferences($resolveReferences)
32                ->addConditionScopeId($scopeId);
33            $scope = $this->fetchOne($query, new Entity());
34            if (! $scope->hasId()) {
35                return null;
36            }
37
38            if (\App::$cache) {
39                \App::$cache->set($cacheKey, $scope);
40                if (\App::$log) {
41                    \App::$log->info('Scope cache set', ['cache_key' => $cacheKey]);
42                }
43            }
44        }
45
46        return $this->readResolvedReferences($scope, $resolveReferences, $disableCache);
47    }
48
49    public function readEntitiesByIds(array $scopeIds, int $resolveReferences = 0, bool $disableCache = false): array
50    {
51        $scopeIds = array_values(array_unique(array_filter(array_map('intval', $scopeIds))));
52        if (!$scopeIds) {
53            return [];
54        }
55
56        $result  = [];
57        $missing = [];
58        foreach ($scopeIds as $scopeId) {
59            $cacheKey = "{$scopeId}-{$resolveReferences}";
60            if (!$disableCache && array_key_exists($cacheKey, self::$cache)) {
61                $result[$scopeId] = self::$cache[$cacheKey];
62            } else {
63                $missing[] = $scopeId;
64            }
65        }
66
67        if ($missing) {
68            $query = new Query\Scope(Query\Base::SELECT);
69            $query
70                ->addEntityMapping()
71                ->addResolvedReferences($resolveReferences)
72                ->addConditionScopeIds($missing);
73
74            $fetched = $this->fetchList($query, new Entity());
75            foreach ($fetched as $entity) {
76                if (!$entity->hasId()) {
77                    continue;
78                }
79                $entity = $this->readResolvedReferences($entity, $resolveReferences);
80                $result[$entity->id] = $entity;
81                self::$cache["{$entity->id}-{$resolveReferences}"] = $entity;
82            }
83        }
84
85        $ordered = [];
86        foreach ($scopeIds as $id) {
87            if (isset($result[$id])) {
88                $ordered[$id] = $result[$id];
89            }
90        }
91
92        return $ordered;
93    }
94
95    #[\Override]
96    public function readResolvedReferences(
97        \BO\Zmsentities\Schema\Entity $entity,
98        $resolveReferences,
99        $disableCache = false
100    ) {
101        if (0 < $resolveReferences) {
102            $entity['dayoff'] = (new DayOff())->readByScopeId($entity->id, $disableCache);
103            $entity['closure'] = (new Closure())->readByScopeId($entity->id, $disableCache);
104        }
105        return $entity;
106    }
107
108    public function readByClusterId(
109        $clusterId,
110        $resolveReferences = 0,
111        $disableCache = false
112    ) {
113        $cacheKey = "scopeReadByClusterId-$clusterId-$resolveReferences";
114
115        if (!$disableCache && \App::$cache && \App::$cache->has($cacheKey)) {
116            $result = \App::$cache->get($cacheKey);
117        }
118
119        if (empty($result)) {
120            if ($resolveReferences > 0) {
121                $query = new Query\Scope(Query\Base::SELECT);
122                $query->addEntityMapping()
123                    ->addResolvedReferences($resolveReferences - 1)
124                    ->addConditionClusterId($clusterId);
125                $result = $this->fetchList($query, new Entity());
126            } else {
127                $result = $this->getReader()->perform(
128                    (new Query\Scope(Query\Base::SELECT))->getQuerySimpleClusterMatch(),
129                    [$clusterId]
130                );
131            }
132
133            if (\App::$cache && !($result instanceof \PDOStatement)) {
134                \App::$cache->set($cacheKey, $result);
135                if (\App::$log) {
136                    \App::$log->info('Scope cache set', ['cache_key' => $cacheKey]);
137                }
138            }
139        }
140
141        $scopeList = new Collection();
142        if (!$result) {
143            return $scopeList;
144        }
145
146        foreach ($result as $entity) {
147            if (0 == $resolveReferences) {
148                $entity = new Entity(
149                    array(
150                        'id' => $entity['id'],
151                        'contact' => ['name' => $entity['contact__name']],
152                        'shortName' => $entity['shortName'] ?? '',
153                        '$ref' => '/scope/' . $entity['id'] . '/'
154                    )
155                );
156                $scopeList->addEntity($entity);
157            } else {
158                $scopeList->addEntity($this->readResolvedReferences(
159                    $entity,
160                    $resolveReferences - 1,
161                    $disableCache
162                ));
163            }
164        }
165
166        return $scopeList;
167    }
168
169    public function readByProviderId($providerId, $resolveReferences = 0, $disableCache = false)
170    {
171        $cacheKey = "scopeReadByProviderId-$providerId-$resolveReferences";
172
173        if (!$disableCache && \App::$cache && \App::$cache->has($cacheKey)) {
174            $result = \App::$cache->get($cacheKey);
175        }
176
177        if (empty($result)) {
178            $query = new Query\Scope(Query\Base::SELECT);
179            $query->addEntityMapping()
180                ->addResolvedReferences($resolveReferences)
181                ->addConditionProviderId($providerId);
182            $result = $this->fetchList($query, new Entity());
183
184            if (\App::$cache) {
185                \App::$cache->set($cacheKey, $result);
186                if (\App::$log) {
187                    \App::$log->info('Scope cache set', ['cache_key' => $cacheKey]);
188                }
189            }
190        }
191
192        $scopeList = new Collection();
193        if (count($result)) {
194            foreach ($result as $entity) {
195                if (0 == $resolveReferences) {
196                    $entity = new Entity(
197                        array(
198                            'id' => $entity->id,
199                            '$ref' => '/scope/' . $entity->id . '/'
200                        )
201                    );
202                    $scopeList->addEntity($entity);
203                } else {
204                    if ($entity instanceof Entity) {
205                        $entity = $this->readResolvedReferences(
206                            $entity,
207                            $resolveReferences - 1,
208                            $disableCache
209                        );
210                        $scopeList->addEntity($entity);
211                    }
212                }
213            }
214        }
215
216        return $scopeList;
217    }
218
219    public function readByRequestId($requestId, $source, $resolveReferences = 0)
220    {
221        $scopeList = new Collection();
222        $providerList = (new Provider())->readListBySource($source, 0, true, $requestId);
223
224        foreach ($providerList as $provider) {
225            $scopeListByProvider = $this->readByProviderId($provider->getId(), $resolveReferences);
226            if ($scopeListByProvider->count()) {
227                $scopeList->addList($scopeListByProvider);
228            }
229        }
230        return $scopeList->withUniqueScopes();
231    }
232
233    public function readByDepartmentId($departmentId, $resolveReferences = 0, $disableCache = false)
234    {
235        $cacheKey = "scopeReadByDepartmentId-$departmentId-$resolveReferences";
236
237        if (!$disableCache && \App::$cache && \App::$cache->has($cacheKey)) {
238            $result = \App::$cache->get($cacheKey);
239        }
240
241        $scopeList = new Collection();
242
243        if (empty($result)) {
244            if ($resolveReferences > 0) {
245                $query = new Query\Scope(Query\Base::SELECT);
246                $query->addEntityMapping()
247                    ->addResolvedReferences($resolveReferences)
248                    ->addConditionDepartmentId($departmentId);
249                $result = $this->fetchList($query, new Entity());
250
251                if (\App::$cache) {
252                    \App::$cache->set($cacheKey, $result);
253                    if (\App::$log) {
254                        \App::$log->info('Scope cache set', ['cache_key' => $cacheKey]);
255                    }
256                }
257            } else {
258                $result = $this->getReader()->perform(
259                    (new Query\Scope(Query\Base::SELECT))->getQuerySimpleDepartmentMatch(),
260                    [$departmentId]
261                );
262            }
263        }
264
265        if ($result) {
266            foreach ($result as $entity) {
267                if (0 == $resolveReferences) {
268                    $entity = new Entity(
269                        array(
270                            'id' => $entity['id'],
271                            'contact' => ['name' => $entity['contact__name']],
272                            'shortName' => $entity['shortName'] ?? '',
273                            '$ref' => '/scope/' . $entity['id'] . '/'
274                        )
275                    );
276                    $scopeList->addEntity($entity);
277                } else {
278                    if ($entity instanceof Entity) {
279                        $entity = $this->readResolvedReferences($entity, $resolveReferences, $disableCache);
280                        $scopeList->addEntity($entity);
281                    }
282                }
283            }
284        }
285
286        return $scopeList;
287    }
288    public function readListBySource($source, $resolveReferences = 0)
289    {
290        $this->testSource($source);
291        $query = new Query\Request(Query\Base::SELECT);
292        $query->setResolveLevel($resolveReferences);
293        $query->addConditionRequestSource($source);
294        $query->addEntityMapping();
295        $requestList = $this->readCollection($query);
296        return ($requestList->count()) ? $requestList->sortByCustomKey('id') : $requestList;
297    }
298
299    protected function testSource($source)
300    {
301        if (! (new Source())->readEntity($source)) {
302            throw new Exception\Source\UnknownDataSource();
303        }
304    }
305
306    protected function readCollection($query)
307    {
308        $requestList = new Collection();
309        $statement = $this->fetchStatement($query);
310        while ($requestData = $statement->fetch(\PDO::FETCH_ASSOC)) {
311            $request = new Entity($query->postProcessJoins($requestData));
312            $requestList->addEntity($request);
313        }
314        return $requestList;
315    }
316
317    public function readList($resolveReferences = 0, $disableCache = false)
318    {
319        $cacheKey = "scopeReadList-$resolveReferences";
320
321        if (!$disableCache && \App::$cache && \App::$cache->has($cacheKey)) {
322            $result = \App::$cache->get($cacheKey);
323        }
324
325        if (empty($result)) {
326            $query = new Query\Scope(Query\Base::SELECT);
327            $query->addEntityMapping()
328                ->addResolvedReferences($resolveReferences);
329            $result = $this->fetchList($query, new Entity());
330
331            if (\App::$cache) {
332                \App::$cache->set($cacheKey, $result);
333                if (\App::$log) {
334                    \App::$log->info('Scope cache set', ['cache_key' => $cacheKey]);
335                }
336            }
337        }
338
339        $scopeList = new Collection();
340        if (count($result)) {
341            foreach ($result as $entity) {
342                if ($entity instanceof Entity) {
343                    $entity = $this->readResolvedReferences($entity, $resolveReferences);
344                    $scopeList->addEntity($entity);
345                }
346            }
347        }
348        return $scopeList;
349    }
350
351    public function readIsOpened($scopeId, $now)
352    {
353        $isOpened = false;
354        $availabilityList = (new Availability())->readOpeningHoursListByDate($scopeId, $now, 2);
355        if ($availabilityList->isOpened($now)) {
356            $isOpened = true;
357        }
358        return $isOpened;
359    }
360
361    public function readIsEnabled($scopeId, $now)
362    {
363        $query = new Query\Scope(Query\Base::SELECT);
364        $query->addEntityMapping()
365            ->setResolveLevel(0)
366            ->addConditionScopeId($scopeId);
367        $scope = $this->fetchOne($query, new Entity());
368        return (
369            $this->readIsOpened($scopeId, $now) &&
370            $this->readIsGivenNumberInContingent($scopeId) &&
371            ! $scope->getStatus('ticketprinter', 'deactivated')
372        );
373    }
374
375    public function readWaitingNumberUpdated($scopeId, $dateTime, $respectContingent = true)
376    {
377        if (! $this->readIsGivenNumberInContingent($scopeId) && $respectContingent) {
378            throw new Exception\Scope\GivenNumberCountExceeded();
379        }
380        $this->perform(
381            (new Query\Scope(Query\Base::SELECT))->getQueryLastWaitingNumber(),
382            ['scope_id' => $scopeId]
383        );
384        $entity = $this->readEntity($scopeId, 0, true)->updateStatusQueue($dateTime);
385        $scope = $this->updateEntity($scopeId, $entity);
386        return $scope->getStatus('queue', 'lastGivenNumber');
387    }
388
389    public function readDisplayNumberUpdated($scopeId)
390    {
391        $this->perform(
392            (new Query\Scope(Query\Base::SELECT))->getQueryLastDisplayNumber(),
393            ['scope_id' => $scopeId]
394        );
395        $entity = $this->readEntity($scopeId, 0, true)->incrementDisplayNumber();
396        $scope = $this->updateEntity($scopeId, $entity);
397        return $scope->getStatus('queue', 'lastDisplayNumber');
398    }
399
400    public function readIsGivenNumberInContingent($scopeId)
401    {
402        $isInContingent = $this->getReader()
403            ->fetchValue((new Query\Scope(Query\Base::SELECT))
404            ->getQueryGivenNumbersInContingent(), ['scope_id' => $scopeId]);
405        return ($isInContingent) ? true : false;
406    }
407
408    public function readQueueList($scopeIds, $dateTime, $resolveReferences = 0, $withEntities = [])
409    {
410        if ($resolveReferences > 0) {
411            $queueList = (new Process())
412                ->readProcessListByScopesAndTime(
413                    $scopeIds,
414                    $dateTime,
415                    $resolveReferences - 1,
416                    $withEntities
417                )
418                ->toQueueList($dateTime);
419        } else {
420            $queueList = (new Queue())
421                ->readListByScopeAndTime($scopeIds, $dateTime, $resolveReferences);
422        }
423
424        return $queueList->withSortedArrival();
425    }
426
427    public function readWithWorkstationCount($scopeId, $dateTime, $resolveReferences = 0, $withEntities = [])
428    {
429        $query = new Query\Scope(Query\Base::SELECT, '', false, null, $withEntities);
430        $query
431            ->addEntityMapping()
432            ->addConditionScopeId($scopeId)
433            ->addResolvedReferences($resolveReferences)
434            ->addSelectWorkstationCount($dateTime);
435        $scope = $this->fetchOne($query, new Entity());
436        $scope = $this->readResolvedReferences($scope, $resolveReferences);
437        return ($scope->hasId()) ? $scope : null;
438    }
439
440    public function readQueueListWithWaitingTime($scope, $dateTime, $resolveReferences = 0, $withEntities = [])
441    {
442        $timeAverage = (int) $scope->getPreference('queue', 'processingTimeAverage');
443        if ($timeAverage <= 0) {
444            $scope = (new Scope())->readEntity($scope->id);
445            $timeAverage = (int) $scope->getPreference('queue', 'processingTimeAverage');
446        }
447        if ($timeAverage <= 0) {
448            $timeAverage = 12;
449        }
450        $queueList = $this->readQueueList([$scope->id], $dateTime, $resolveReferences, $withEntities);
451        $workstationCount = $scope->getCalculatedWorkstationCount();
452        return $queueList->withEstimatedWaitingTime($timeAverage, $workstationCount, $dateTime);
453    }
454
455    public function readScopesQueueListWithWaitingTime(Collection $scopes, $dateTime, $resolveReferences = 0, $withEntities = [])
456    {
457        $timeSum = 0;
458        $workstationCount = 0;
459        $scopeIds = [];
460        foreach ($scopes as $scope) {
461            $timeSum += $scope->getPreference('queue', 'processingTimeAverage');
462            $workstationCount += $scope->getCalculatedWorkstationCount();
463            $scopeIds[] = $scope->id;
464        }
465
466        $timeAverage = (int) round($timeSum / $scopes->count());
467        if ($timeAverage <= 0) {
468            $timeAverage = 12;
469        }
470        $queueList = $this->readQueueList($scopeIds, $dateTime, $resolveReferences, $withEntities);
471
472        return $queueList->withEstimatedWaitingTime($timeAverage, $workstationCount, $dateTime);
473    }
474
475    public function readListWithScopeAdminEmail($resolveReferences = 0)
476    {
477        $scopeList = new Collection();
478        $query = new Query\Scope(Query\Base::SELECT);
479        $query
480            ->addEntityMapping()
481            ->addConditionWithAdminEmail()
482            ->addResolvedReferences($resolveReferences);
483        $result = $this->fetchList($query, new Entity());
484        if (count($result)) {
485            foreach ($result as $entity) {
486                if ($entity instanceof Entity) {
487                    $entity = $this->readResolvedReferences($entity, $resolveReferences);
488                    $scopeList->addEntity($entity);
489                }
490            }
491        }
492        return $scopeList;
493    }
494
495    public function writeEntity(\BO\Zmsentities\Scope $entity, $parentId)
496    {
497        self::$cache = [];
498        $query = new Query\Scope(Query\Base::INSERT);
499        $values = $query->reverseEntityMapping($entity, $parentId);
500        $query->addValues($values);
501        $this->writeItem($query);
502        $lastInsertId = $this->getWriter()
503            ->lastInsertId();
504        $this->replacePreferences($entity);
505
506        $this->removeCacheByContext($entity, $parentId);
507
508        return $this->readEntity($lastInsertId);
509    }
510
511    public function updateEntity($scopeId, \BO\Zmsentities\Scope $entity, $resolveReferences = 0)
512    {
513        self::$cache = [];
514
515        $departmentId = $this->readDepartmentIdByScopeId($scopeId);
516
517        $query = new Query\Scope(Query\Base::UPDATE);
518        $query->addConditionScopeId($scopeId);
519        $values = $query->reverseEntityMapping($entity);
520        $query->addValues($values);
521        $this->writeItem($query);
522        $this->replacePreferences($entity);
523
524        $this->removeCacheByContext($entity, $departmentId);
525
526        return $this->readEntity($scopeId, $resolveReferences, true);
527    }
528
529    public function replacePreferences(\BO\Zmsentities\Scope $entity)
530    {
531        if (isset($entity['preferences'])) {
532            $preferenceQuery = new Preferences();
533            $entityName = 'scope';
534            $entityId = $entity['id'];
535            foreach ($entity['preferences'] as $groupName => $groupValues) {
536                foreach ($groupValues as $name => $value) {
537                    $preferenceQuery->replaceProperty($entityName, $entityId, $groupName, $name, $value);
538                }
539            }
540        }
541    }
542
543    public function updateGhostWorkstationCount(\BO\Zmsentities\Scope $entity, \DateTimeInterface $dateTime)
544    {
545        $departmentId = $this->readDepartmentIdByScopeId($entity->id);
546
547        $query = new Query\Scope(Query\Base::UPDATE);
548        $query->addConditionScopeId($entity->id);
549        $values = $query->setGhostWorkstationCountEntityMapping($entity, $dateTime);
550        $query->addValues($values);
551        $this->writeItem($query);
552
553        $this->removeCacheByContext($entity, $departmentId);
554
555        return $entity;
556    }
557
558    public function updateEmergency($scopeId, \BO\Zmsentities\Scope $entity)
559    {
560        self::$cache = [];
561        $departmentId = $this->readDepartmentIdByScopeId($scopeId);
562
563        $query = new Query\Scope(Query\Base::UPDATE);
564        $query->addConditionScopeId($scopeId);
565        $values = $query->setEmergencyEntityMapping($entity);
566        $query->addValues($values);
567        $this->writeItem($query);
568
569        $this->removeCacheByContext($entity, $departmentId);
570
571        return $this->readEntity($scopeId, 0, true);
572    }
573
574    public function writeImageData($scopeId, \BO\Zmsentities\Mimepart $entity)
575    {
576        if ($entity->mime && $entity->content) {
577            $this->deleteImage($scopeId);
578            $extension = $entity->getExtension();
579            if ($extension == 'jpeg') {
580                $extension = 'jpg'; //compatibility ZMS1
581            }
582            $imageName = 's_' . $scopeId . '_bild.' . $extension;
583            $this->getWriter()->perform(
584                (new Query\Scope(Query\Base::REPLACE))->getQueryWriteImageData(),
585                array(
586                    'imagename' => $imageName,
587                    'imagedata' => $entity->content
588                )
589            );
590        }
591        $entity->id = $scopeId;
592        return $entity;
593    }
594
595    public function readImageData($scopeId)
596    {
597        $imageName = 's_' . $scopeId . '_bild';
598        $imageData = new \BO\Zmsentities\Mimepart();
599        $fileData = $this->getReader()->fetchAll(
600            (new Query\Scope(Query\Base::SELECT))->getQueryReadImageData(),
601            ['imagename' => "$imageName%"]
602        );
603        if ($fileData) {
604            $imageData->content = $fileData[0]['imagecontent'];
605            $imageData->mime = pathinfo($fileData[0]['imagename'])['extension'];
606        }
607        return $imageData;
608    }
609
610    public function deleteImage($scopeId)
611    {
612        $imageName = 's_' . $scopeId . '_bild';
613        return $this->perform((new Query\Scope(Query\Base::DELETE))->getQueryDeleteImage(), array(
614            'imagename' => "$imageName%"
615        ));
616    }
617
618    public function deleteEntity($scopeId)
619    {
620        $processListCount = (new Process())->readProcessListCountByScope($scopeId);
621        if (0 < $processListCount) {
622            throw new Exception\Scope\ScopeHasProcesses();
623        }
624        self::$cache = [];
625
626        $departmentId = $this->readDepartmentIdByScopeId($scopeId);
627
628        $entity = $this->readEntity($scopeId);
629        $query = new Query\Scope(Query\Base::DELETE);
630        $query->addConditionScopeId($scopeId);
631        $this->deletePreferences($entity);
632
633        $this->removeCacheByContext($entity, $departmentId);
634
635        return ($this->deleteItem($query)) ? $entity : null;
636    }
637
638    public function deletePreferences(\BO\Zmsentities\Scope $entity)
639    {
640        $preferenceQuery = new Preferences();
641        $entityName = 'scope';
642        $entityId = $entity['id'];
643        foreach ($entity['preferences'] as $groupName => $groupValues) {
644            foreach (array_keys($groupValues) as $name) {
645                $preferenceQuery->deleteProperty($entityName, $entityId, $groupName, $name);
646            }
647        }
648    }
649
650    public function readDepartmentIdByScopeId($scopeId)
651    {
652        $query = new Query\Scope(Query\Base::SELECT);
653        return $this->getReader()->fetchValue($query->getQueryDepartmentIdByScopeId(), [$scopeId]);
654    }
655
656    public function readClusterIdsByScopeId($scopeId)
657    {
658        $query = new Query\Scope(Query\Base::SELECT);
659        $result = $this->getReader()->fetchAll($query->getQueryClusterIdsByScopeId(), [$scopeId]);
660        return array_column($result, 'clusterID');
661    }
662
663    public function removeCacheByContext($scope, $departmentId = null)
664    {
665        if (!\App::$cache) {
666            return;
667        }
668
669        $invalidatedKeys = [];
670
671        // Invalidate scope entity cache for all resolveReferences levels (0, 1, 2)
672        if (isset($scope->id)) {
673            for ($resolveReferences = 0; $resolveReferences <= 2; $resolveReferences++) {
674                $key = "scope-{$scope->id}-{$resolveReferences}";
675                if (\App::$cache->has($key)) {
676                    \App::$cache->delete($key);
677                    $invalidatedKeys[] = $key;
678                }
679            }
680        }
681
682        // Invalidate scope list cache for all resolveReferences levels (0, 1, 2)
683        for ($resolveReferences = 0; $resolveReferences <= 2; $resolveReferences++) {
684            $key = "scopeReadList-{$resolveReferences}";
685            if (\App::$cache->has($key)) {
686                \App::$cache->delete($key);
687                $invalidatedKeys[] = $key;
688            }
689        }
690
691        // Invalidate scopeReadByDepartmentId cache for all resolveReferences levels (0, 1, 2)
692        if ($departmentId) {
693            for ($resolveReferences = 0; $resolveReferences <= 2; $resolveReferences++) {
694                $key = "scopeReadByDepartmentId-{$departmentId}-{$resolveReferences}";
695                if (\App::$cache->has($key)) {
696                    \App::$cache->delete($key);
697                    $invalidatedKeys[] = $key;
698                }
699            }
700        }
701
702        // Invalidate scopeReadByClusterId cache for all clusters containing this scope
703        if (isset($scope->id)) {
704            $clusterIds = $this->readClusterIdsByScopeId($scope->id);
705            foreach ($clusterIds as $clusterId) {
706                for ($resolveReferences = 0; $resolveReferences <= 2; $resolveReferences++) {
707                    $key = "scopeReadByClusterId-{$clusterId}-{$resolveReferences}";
708                    if (\App::$cache->has($key)) {
709                        \App::$cache->delete($key);
710                        $invalidatedKeys[] = $key;
711                    }
712                }
713            }
714        }
715
716        // Invalidate scopeReadByProviderId cache for all resolveReferences levels (0, 1, 2)
717        if (isset($scope->provider) && isset($scope->provider['id']) && $scope->provider['id']) {
718            $providerId = $scope->provider['id'];
719            for ($resolveReferences = 0; $resolveReferences <= 2; $resolveReferences++) {
720                $key = "scopeReadByProviderId-{$providerId}-{$resolveReferences}";
721                if (\App::$cache->has($key)) {
722                    \App::$cache->delete($key);
723                    $invalidatedKeys[] = $key;
724                }
725            }
726        }
727
728        if (!empty($invalidatedKeys) && \App::$log) {
729            \App::$log->info('Scope cache invalidated', [
730                'scope_id' => isset($scope->id) ? $scope->id : 'unknown',
731                'invalidated_keys' => $invalidatedKeys
732            ]);
733        }
734    }
735
736    public function removeCache($scope)
737    {
738        $departmentId = isset($scope->id) ? $this->readDepartmentIdByScopeId($scope->id) : null;
739        $this->removeCacheByContext($scope, $departmentId);
740    }
741}