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