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