Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
74.80% covered (warning)
74.80%
92 / 123
76.92% covered (warning)
76.92%
10 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
User
74.80% covered (warning)
74.80%
92 / 123
76.92% covered (warning)
76.92%
10 / 13
103.43
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 readWorkstation
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
8
 testWorkstationAssigend
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
72
 testWorkstationAccessRights
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
5
 testWorkstationAssignedRights
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 checkRights
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 checkDepartments
60.00% covered (warning)
60.00%
21 / 35
0.00% covered (danger)
0.00%
0 / 1
21.22
 checkDepartment
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
5
 hasRights
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 hasXApiKey
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 testWorkstationIsOveraged
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 testReadDepartmentByOrganisation
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 normalizeDepartmentIds
64.29% covered (warning)
64.29%
9 / 14
0.00% covered (danger)
0.00%
0 / 1
6.14
1<?php
2
3namespace BO\Zmsapi\Helper;
4
5use BO\Slim\Render;
6use BO\Zmsdb\Useraccount;
7use BO\Zmsdb\Workstation;
8use BO\Zmsapi\Helper\UserAuth;
9use BO\Zmsentities\Collection\DepartmentList;
10
11/**
12 *
13 * @SuppressWarnings(CouplingBetweenObjects)
14 */
15class User
16{
17    public static $workstation = null;
18    public static $workstationResolved = null;
19
20    public static $assignedWorkstation = null;
21
22    public static $request = null;
23
24    public function __construct($request, $resolveReferences = 0)
25    {
26        static::$request = $request;
27        static::readWorkstation($resolveReferences);
28    }
29
30    public static function readWorkstation($resolveReferences = 0)
31    {
32        $request = (static::$request) ? static::$request : Render::$request;
33        if (! static::$workstation) {
34            $useraccount = UserAuth::getUseraccountByAuthMethod($request);
35            if ($useraccount && $useraccount->hasId()) {
36                static::$workstation = (new Workstation())->readEntity($useraccount->id, $resolveReferences);
37                if ($resolveReferences < 1) {
38                    static::$workstation->useraccount = $useraccount;
39                }
40                static::$workstationResolved = $resolveReferences;
41            } else {
42                static::$workstation = new \BO\Zmsentities\Workstation();
43            }
44        }
45        if ($resolveReferences > static::$workstationResolved && static::$workstation->hasId()) {
46            static::$workstation = (new Workstation())
47                ->readResolvedReferences(static::$workstation, $resolveReferences);
48        }
49        return static::$workstation;
50    }
51
52    /**
53     * @throws \BO\Zmsapi\Exception\Workstation\WorkstationAlreadyAssigned
54     *
55     */
56    public static function testWorkstationAssigend(\BO\Zmsentities\Workstation $entity, $resolveReferences = 0)
57    {
58        if (! static::$assignedWorkstation && $entity->name) {
59            static::$assignedWorkstation = (new Workstation())->readWorkstationByScopeAndName(
60                $entity->scope['id'],
61                $entity->name,
62                $resolveReferences
63            );
64        }
65        if (
66            static::$assignedWorkstation &&
67            static::$assignedWorkstation->id != $entity->id &&
68            static::$assignedWorkstation->name == $entity->name &&
69            static::$assignedWorkstation->scope['id'] == $entity->scope['id'] &&
70            ! static::$assignedWorkstation->getUseraccount()->isOveraged(\App::$now)
71        ) {
72            throw new \BO\Zmsapi\Exception\Workstation\WorkstationAlreadyAssigned();
73        }
74    }
75
76    /**
77     * @throws \BO\Zmsentities\Exception\UserAccountAccessRightsFailed()
78     *
79     */
80    public static function testWorkstationAccessRights($useraccount)
81    {
82        if (
83            (
84                ! static::$workstation->getUseraccount()->isSuperUser() &&
85                ! static::$workstation->hasAccessToUseraccount($useraccount)
86            ) ||
87            (
88                ! static::$workstation->getUseraccount()->isSuperUser() &&
89                $useraccount->isSuperUser()
90            )
91        ) {
92            throw new \BO\Zmsentities\Exception\UserAccountAccessRightsFailed();
93        }
94    }
95
96    /**
97     * @throws  \BO\Zmsentities\Exception\UserAccountMissingRights()
98     *          \BO\Zmsentities\Exception\UserAccountMissingLogin()
99     *
100     */
101    public static function testWorkstationAssignedRights($useraccount)
102    {
103        static::$workstation
104            ->getUseraccount()
105            ->testRights(
106                array_keys(
107                    array_filter($useraccount->rights, function ($right) {
108                        return (1 == $right);
109                    })
110                )
111            );
112    }
113
114    /**
115     * @return \BO\Zmsentities\Workstation
116     *
117     */
118    public static function checkRights()
119    {
120        $workstation = static::readWorkstation();
121        if (\App::RIGHTSCHECK_ENABLED) {
122            $workstation->getUseraccount()->testRights(func_get_args());
123        }
124        return $workstation;
125    }
126
127    public static function checkDepartments($departmentIds)
128    {
129        $normalizedIds = self::normalizeDepartmentIds($departmentIds);
130        $departments = new DepartmentList();
131
132        if (empty($normalizedIds)) {
133            return $departments;
134        }
135
136        $workstation = static::readWorkstation(2);
137        $userAccount = $workstation->getUseraccount();
138
139        if (! $userAccount->hasId()) {
140            throw new \BO\Zmsentities\Exception\UseraccountMissingLogin();
141        }
142
143        if ($userAccount->isSuperUser()) {
144            // Bulk-load all departments in one query for superusers
145            $departmentMap = (new \BO\Zmsdb\Department())->readEntitiesByIds($normalizedIds, 1);
146            foreach ($normalizedIds as $departmentId) {
147                if (!isset($departmentMap[$departmentId])) {
148                    throw new \BO\Zmsentities\Exception\UserAccountMissingDepartment(
149                        "No access to department " . htmlspecialchars((string) $departmentId)
150                    );
151                }
152                $departments->addEntity($departmentMap[$departmentId]);
153            }
154        } elseif ($userAccount->hasRights(['department'])) {
155            // Users with 'department' rights: need organisation-based access checks
156            // Group departments by organisation and load in batches
157            foreach ($normalizedIds as $departmentId) {
158                $departments->addEntity(self::checkDepartment($departmentId));
159            }
160        } else {
161            // Regular users: extract departments directly from already-loaded user department list
162            $userDepartmentList = $userAccount->getDepartmentList();
163            $accessibleDepartmentIds = $userDepartmentList->getIds();
164            $accessibleRequestedIds = array_intersect($normalizedIds, $accessibleDepartmentIds);
165
166            if (count($accessibleRequestedIds) !== count($normalizedIds)) {
167                // Some requested departments are not accessible
168                $missingIds = array_diff($normalizedIds, $accessibleRequestedIds);
169                throw new \BO\Zmsentities\Exception\UserAccountMissingDepartment(
170                    "No access to department(s): " . implode(', ', array_map('htmlspecialchars', $missingIds))
171                );
172            }
173
174            // Extract requested departments from already-loaded list (no DB query needed)
175            foreach ($accessibleRequestedIds as $departmentId) {
176                $department = $userDepartmentList->getEntity($departmentId);
177                if (!$department || !$department->hasId()) {
178                    throw new \BO\Zmsentities\Exception\UserAccountMissingDepartment(
179                        "No access to department " . htmlspecialchars((string) $departmentId)
180                    );
181                }
182                $departments->addEntity($department);
183            }
184        }
185
186        return $departments;
187    }
188
189    /**
190     * @return \BO\Zmsentities\Department
191     *
192     */
193    public static function checkDepartment($departmentId)
194    {
195        $workstation = static::readWorkstation(2);
196        $userAccount = $workstation->getUseraccount();
197        if (! $userAccount->hasId()) {
198            throw new \BO\Zmsentities\Exception\UseraccountMissingLogin();
199        }
200        if ($userAccount->isSuperUser()) {
201            $department = (new \BO\Zmsdb\Department())->readEntity($departmentId);
202        } elseif ($userAccount->hasRights(['department'])) {
203            $department = self::testReadDepartmentByOrganisation($departmentId, $userAccount);
204        } else {
205            $department = $userAccount->testDepartmentById($departmentId);
206        }
207        if (! $department) {
208            throw new \BO\Zmsentities\Exception\UserAccountMissingDepartment(
209                "No access to department " . htmlspecialchars($departmentId)
210            );
211        }
212        return $department;
213    }
214
215    public static function hasRights()
216    {
217        $userAccount = static::readWorkstation()->getUseraccount();
218        return $userAccount->hasId();
219    }
220
221    /**
222     * Get X-Api-Key from header
223     *
224    */
225    public static function hasXApiKey($request)
226    {
227        $xApiKeyEntity = null;
228        $xApiKey = $request->getHeaderLine('x-api-key');
229        if ($xApiKey) {
230            $xApiKeyEntity = (new \BO\Zmsdb\Apikey())->readEntity($xApiKey);
231        }
232        return ($xApiKeyEntity && $xApiKeyEntity->hasId());
233    }
234
235    public static function testWorkstationIsOveraged($workstation)
236    {
237        if ($workstation->hasId() && $workstation->getUseraccount()->isOveraged(\App::$now)) {
238            $exception = new \BO\Zmsapi\Exception\Useraccount\AuthKeyFound();
239            $exception->data = $workstation;
240            throw $exception;
241        }
242    }
243
244    protected static function testReadDepartmentByOrganisation($departmentId, $userAccount)
245    {
246        $organisation = (new \BO\Zmsdb\Organisation())->readByDepartmentId($departmentId, 1);
247        $organisation->departments = $organisation->getDepartmentList()->withAccess($userAccount);
248        $department = $organisation->departments->getEntity($departmentId);
249        return $department;
250    }
251
252    public static function normalizeDepartmentIds(array $departmentIds)
253    {
254        $normalized = [];
255        foreach ($departmentIds as $departmentId) {
256            if ($departmentId === null) {
257                continue;
258            }
259            $departmentId = trim((string) $departmentId);
260            if ($departmentId === '') {
261                continue;
262            }
263            $validatedId = filter_var($departmentId, FILTER_VALIDATE_INT);
264            if ($validatedId === false) {
265                throw new \BO\Zmsapi\Exception\BadRequest(
266                    "Invalid department ID: " . htmlspecialchars($departmentId)
267                );
268            }
269            $normalized[] = $validatedId;
270        }
271
272        return array_values(array_unique($normalized));
273    }
274}