Passed
Push — master ( 0c1a75...92d143 )
by Gaetano
07:13
created

RoleManager   F

Complexity

Total Complexity 63

Size/Duplication

Total Lines 414
Duplicated Lines 0 %

Test Coverage

Coverage 81.53%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 181
dl 0
loc 414
ccs 128
cts 157
cp 0.8153
rs 3.36
c 3
b 0
f 0
wmc 63

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A load() 0 7 1
A create() 0 27 5
A listAllowedConditions() 0 3 1
A addPolicy() 0 12 3
A matchRoles() 0 17 4
A getReferencesValues() 0 22 6
C update() 0 55 12
B assignRole() 0 38 10
B generateMigration() 0 69 9
A delete() 0 13 2
A unassignRole() 0 23 6
A createLimitation() 0 19 3

How to fix   Complexity   

Complex Class

Complex classes like RoleManager often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use RoleManager, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Kaliop\eZMigrationBundle\Core\Executor;
4
5
use eZ\Publish\API\Repository\Values\User\Role;
6
use eZ\Publish\API\Repository\RoleService;
7
use eZ\Publish\API\Repository\UserService;
8
use Kaliop\eZMigrationBundle\API\Collection\RoleCollection;
9
use Kaliop\eZMigrationBundle\API\MigrationGeneratorInterface;
10
use Kaliop\eZMigrationBundle\API\EnumerableMatcherInterface;
11
use Kaliop\eZMigrationBundle\Core\Helper\LimitationConverter;
12
use Kaliop\eZMigrationBundle\Core\Matcher\RoleMatcher;
13
use eZ\Publish\API\Repository\Values\User\Limitation;
14
15
/**
16
 * Handles role migrations.
17
 */
18
class RoleManager extends RepositoryExecutor implements MigrationGeneratorInterface, EnumerableMatcherInterface
19
{
20
    protected $supportedStepTypes = array('role');
21
    protected $supportedActions = array('create', 'load', 'update', 'delete');
22
23
    protected $limitationConverter;
24
    protected $roleMatcher;
25
26 96
    public function __construct(RoleMatcher $roleMatcher, LimitationConverter $limitationConverter)
27
    {
28 96
        $this->roleMatcher = $roleMatcher;
29 96
        $this->limitationConverter = $limitationConverter;
30 96
    }
31
32
    /**
33
     * Method to handle the create operation of the migration instructions
34
     */
35 1
    protected function create($step)
36
    {
37 1
        $roleService = $this->repository->getRoleService();
38 1
        $userService = $this->repository->getUserService();
39
40 1
        $roleName = $this->referenceResolver->resolveReference($step->dsl['name']);
41 1
        $roleCreateStruct = $roleService->newRoleCreateStruct($roleName);
42
43
        // Publish new role
44 1
        $role = $roleService->createRole($roleCreateStruct);
45 1
        if (is_callable(array($roleService, 'publishRoleDraft'))) {
46 1
            $roleService->publishRoleDraft($role);
0 ignored issues
show
Bug introduced by
The method publishRoleDraft() does not exist on eZ\Publish\API\Repository\RoleService. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

46
            $roleService->/** @scrutinizer ignore-call */ 
47
                          publishRoleDraft($role);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
47
        }
48
49 1
        if (isset($step->dsl['policies'])) {
50 1
            foreach ($step->dsl['policies'] as $key => $ymlPolicy) {
51 1
                $this->addPolicy($role, $roleService, $ymlPolicy);
52
            }
53
        }
54
55 1
        if (isset($step->dsl['assign'])) {
56
            $this->assignRole($role, $roleService, $userService, $step->dsl['assign']);
57
        }
58
59 1
        $this->setReferences($role, $step);
60
61 1
        return $role;
62
    }
63
64
    protected function load($step)
65
    {
66
        $roleCollection = $this->matchRoles('load', $step);
67
68
        $this->setReferences($roleCollection, $step);
69
70
        return $roleCollection;
71
    }
72
73
    /**
74
     * Method to handle the update operation of the migration instructions
75
     */
76 1
    protected function update($step)
77
    {
78 1
        $roleCollection = $this->matchRoles('update', $step);
79
80 1
        if (count($roleCollection) > 1 && isset($step->dsl['references'])) {
81
            throw new \Exception("Can not execute Role update because multiple roles match, and a references section is specified in the dsl. References can be set when only 1 role matches");
82
        }
83
84 1
        if (count($roleCollection) > 1 && isset($step->dsl['new_name'])) {
85
            throw new \Exception("Can not execute Role update because multiple roles match, and a new_name is specified in the dsl.");
86
        }
87
88 1
        $roleService = $this->repository->getRoleService();
89 1
        $userService = $this->repository->getUserService();
90
91
        /** @var \eZ\Publish\API\Repository\Values\User\Role $role */
92 1
        foreach ($roleCollection as $key => $role) {
93
94
            // Updating role name
95 1
            if (isset($step->dsl['new_name'])) {
96
                $update = $roleService->newRoleUpdateStruct();
97
                $newRoleName = $this->referenceResolver->resolveReference($step->dsl['new_name']);
98
                $update->identifier = $this->referenceResolver->resolveReference($newRoleName);
99
                $role = $roleService->updateRole($role, $update);
100
            }
101
102 1
            if (isset($step->dsl['policies'])) {
103 1
                $ymlPolicies = $step->dsl['policies'];
104
105
                // Removing all policies so we can add them back.
106
                // TODO: Check and update policies instead of remove and add.
107 1
                $policies = $role->getPolicies();
108 1
                foreach ($policies as $policy) {
109
                    $roleService->deletePolicy($policy);
110
                }
111
112 1
                foreach ($ymlPolicies as $ymlPolicy) {
113 1
                    $this->addPolicy($role, $roleService, $ymlPolicy);
114
                }
115
            }
116
117 1
            if (isset($step->dsl['assign'])) {
118 1
                $this->assignRole($role, $roleService, $userService, $step->dsl['assign']);
119
            }
120
121 1
            if (isset($step->dsl['unassign'])) {
122
                $this->unassignRole($role, $roleService, $userService, $step->dsl['unassign']);
123
            }
124 1
125
            $roleCollection[$key] = $role;
126 1
        }
127
128
        $this->setReferences($roleCollection, $step);
129
130
        return $roleCollection;
131
    }
132 1
133
    /**
134 1
     * Method to handle the delete operation of the migration instructions
135
     */
136 1
    protected function delete($step)
137
    {
138 1
        $roleCollection = $this->matchRoles('delete', $step);
139
140 1
        $this->setReferences($roleCollection, $step);
141 1
142
        $roleService = $this->repository->getRoleService();
143
144 1
        foreach ($roleCollection as $role) {
145
            $roleService->deleteRole($role);
146
        }
147
148
        return $roleCollection;
149
    }
150
151
    /**
152 1
     * @param string $action
153
     * @return RoleCollection
154 1
     * @throws \Exception
155
     */
156
    protected function matchRoles($action, $step)
157
    {
158
        if (!isset($step->dsl['name']) && !isset($step->dsl['match'])) {
159 1
            throw new \Exception("The name of a role or a match condition is required to $action it");
160 1
        }
161
162 1
        // Backwards compat
163
        if (isset($step->dsl['match'])) {
164
            $match = $step->dsl['match'];
165
        } else {
166 1
            $match = array('identifier' => $step->dsl['name']);
167
        }
168 1
169
        // convert the references passed in the match
170
        $match = $this->resolveReferencesRecursively($match);
171
172
        return $this->roleMatcher->match($match);
173
    }
174
175
    /**
176
     * @param Role $role
177 1
     * @param array $references the definitions of the references to set
178
     * @throws \InvalidArgumentException When trying to assign a reference to an unsupported attribute
179 1
     * @return array key: the reference names, values: the reference values
180
     */
181 1
    protected function getReferencesValues($role, array $references, $step)
182 1
    {
183 1
        $refs = array();
184 1
185 1
        foreach ($references as $reference) {
186 1
            switch ($reference['attribute']) {
187 1
                case 'role_id':
188
                case 'id':
189 1
                    $value = $role->id;
190 1
                    break;
191
                case 'identifier':
192
                case 'role_identifier':
193
                    $value = $role->identifier;
194
                    break;
195 1
                default:
196
                    throw new \InvalidArgumentException('Role Manager does not support setting references for attribute ' . $reference['attribute']);
197
            }
198 1
199
            $refs[$reference['identifier']] = $value;
200
        }
201
202
        return $refs;
203
    }
204
205
    /**
206
     * @param array $matchCondition
207
     * @param string $mode
208 5
     * @param array $context
209
     * @throws \Exception
210 5
     * @return array
211 5
     */
212 5
    public function generateMigration(array $matchCondition, $mode, array $context = array())
213
    {
214
        $previousUserId = $this->loginUser($this->getAdminUserIdentifierFromContext($context));
215 5
        $roleCollection = $this->roleMatcher->match($matchCondition);
216
        $data = array();
217 5
218 5
        /** @var \eZ\Publish\API\Repository\Values\User\Role $role */
219
        foreach ($roleCollection as $role) {
220
            $roleData = array(
221
                'type' => reset($this->supportedStepTypes),
222 5
                'mode' => $mode
223 2
            );
224 2
225
            switch ($mode) {
226 2
                case 'create':
227
                    $roleData = array_merge(
228
                        $roleData,
229 2
                        array(
230 3
                            'name' => $role->identifier
231 2
                        )
232 3
                    );
233 3
                    break;
234
                case 'update':
235
                case 'delete':
236 3
                    $roleData = array_merge(
237
                        $roleData,
238
                        array(
239
                            'match' => array(
240 3
                                RoleMatcher::MATCH_ROLE_IDENTIFIER => $role->identifier
241
                            )
242
                        )
243
                    );
244
                    break;
245 5
                default:
246 3
                    throw new \Exception("Executor 'role' doesn't support mode '$mode'");
247 3
            }
248 3
249
            if ($mode != 'delete') {
250 3
                $policies = array();
251 3
                foreach ($role->getPolicies() as $policy) {
252
                    $limitations = array();
253
254 3
                    foreach ($policy->getLimitations() as $limitation) {
255
                        if (!($limitation instanceof Limitation)) {
256
                            throw new \Exception("The role contains an invalid limitation for policy {$policy->module}/{$policy->function}, we can not reliably generate its definition.");
257 3
                        }
258 3
                        $limitations[] = $this->limitationConverter->getLimitationArrayWithIdentifiers($limitation);
259 3
                    }
260 3
261
                    $policies[] = array(
262
                        'module' => $policy->module,
263
                        'function' => $policy->function,
264 3
                        'limitations' => $limitations
265 3
                    );
266
                }
267 3
268
                $roleData = array_merge(
269
                    $roleData,
270
                    array(
271
                        'policies' => $policies
272 5
                    )
273
                );
274
            }
275 5
276 5
            $data[] = $roleData;
277
        }
278
279
        $this->loginUser($previousUserId);
280
        return $data;
281
    }
282
283
    /**
284
     * @return string[]
285
     */
286
    public function listAllowedConditions()
287
    {
288
        return $this->roleMatcher->listAllowedConditions();
289
    }
290
291
    /**
292
     * Create a new Limitation object based on the type and value in the $limitation array.
293
     *
294
     * <pre>
295
     * $limitation = array(
296
     *  'identifier' => Type of the limitation
297
     *  'values' => array(Values to base the limitation on)
298
     * )
299
     * </pre>
300
     *
301 1
     * @param \eZ\Publish\API\Repository\RoleService $roleService
302
     * @param array $limitation
303 1
     * @return Limitation
304
     */
305 1
    protected function createLimitation(RoleService $roleService, array $limitation)
306
    {
307 1
        $limitationType = $roleService->getLimitationType($limitation['identifier']);
308 1
309
        // 1st resolve refs (if we got passed a string)
310 1
        $limitationValue = $this->referenceResolver->resolveReference($limitation['values']);
311 1
        // then, if we have an array, resolve refs recursively
312
        if (is_array($limitationValue)) {
313
            foreach ($limitationValue as $id => $value) {
314
                $limitationValue[$id] = $this->referenceResolver->resolveReference($value);
315
            }
316
        } else {
317
            // if still a scalar, make sure we can loop over it
318
            $limitationValue = array($limitationValue);
319
        }
320
321
322
        $limitationValue = $this->limitationConverter->resolveLimitationValue($limitation['identifier'], $limitationValue);
323
        return $limitationType->buildValue($limitationValue);
324
    }
325
326
    /**
327
     * Assign a role to users and groups in the assignment array.
328
     *
329
     * <pre>
330
     * $assignments = array(
331
     *      array(
332 1
     *          'type' => 'user',
333
     *          'ids' => array(user ids),
334 1
     *          'limitation' => array(limitations)
335 1
     *      )
336 1
     * )
337 1
     * </pre>
338 1
     *
339
     * @param \eZ\Publish\API\Repository\Values\User\Role $role
340 1
     * @param \eZ\Publish\API\Repository\RoleService $roleService
341
     * @param \eZ\Publish\API\Repository\UserService $userService
342 1
     * @param array $assignments
343
     */
344
    protected function assignRole(Role $role, RoleService $roleService, UserService $userService, array $assignments)
345 1
    {
346 1
        foreach ($assignments as $assign) {
347 1
            switch ($assign['type']) {
348
                case 'user':
349
                    foreach ($assign['ids'] as $userId) {
350
                        $userId = $this->referenceResolver->resolveReference($userId);
351 1
352
                        $user = $userService->loadUser($userId);
353
354
                        if (!isset($assign['limitations'])) {
355
                            $roleService->assignRoleToUser($role, $user);
356
                        } else {
357
                            foreach ($assign['limitations'] as $limitation) {
358
                                $limitationObject = $this->createLimitation($roleService, $limitation);
359
                                $roleService->assignRoleToUser($role, $user, $limitationObject);
360
                            }
361
                        }
362
                    }
363
                    break;
364
                case 'group':
365
                    foreach ($assign['ids'] as $groupId) {
366
                        $groupId = $this->referenceResolver->resolveReference($groupId);
367
368
                        $group = $userService->loadUserGroup($groupId);
369
370
                        if (!isset($assign['limitations'])) {
371
                            $roleService->assignRoleToUserGroup($role, $group);
372
                        } else {
373 1
                            foreach ($assign['limitations'] as $limitation) {
374
                                $limitationObject = $this->createLimitation($roleService, $limitation);
375
                                $roleService->assignRoleToUserGroup($role, $group, $limitationObject);
376 1
                            }
377
                        }
378
                    }
379
                    break;
380
                default:
381
                    throw new \Exception("Unsupported type '{$assign['type']}'");
382
            }
383
        }
384
    }
385 1
386
    protected function unassignRole(Role $role, RoleService $roleService, UserService $userService, array $assignments)
387 1
    {
388
        foreach ($assignments as $assign) {
389 1
            switch ($assign['type']) {
390 1
                case 'user':
391 1
                    foreach ($assign['ids'] as $userId) {
392 1
                        $userId = $this->referenceResolver->resolveReference($userId);
393
394
                        $user = $userService->loadUser($userId);
395
396 1
                        $roleService->unassignRoleFromUser($role, $user);
397 1
                    }
398
                    break;
399
                case 'group':
400
                    foreach ($assign['ids'] as $groupId) {
401
                        $groupId = $this->referenceResolver->resolveReference($groupId);
402
403
                        $group = $userService->loadUserGroup($groupId);
404
                        $roleService->unassignRoleFromUserGroup($role, $group);
405
                    }
406
                    break;
407
                default:
408
                    throw new \Exception("Unsupported type '{$assign['type']}'");
409
            }
410
        }
411
    }
412
413
    /**
414
     * Add new policies to the $role Role.
415
     *
416
     * @param \eZ\Publish\API\Repository\Values\User\Role $role
417
     * @param \eZ\Publish\API\Repository\RoleService $roleService
418
     * @param array $policy
419
     */
420
    protected function addPolicy(Role $role, RoleService $roleService, array $policy)
421
    {
422
        $policyCreateStruct = $roleService->newPolicyCreateStruct($policy['module'], $policy['function']);
423
424
        if (array_key_exists('limitations', $policy)) {
425
            foreach ($policy['limitations'] as $limitation) {
426
                $limitationObject = $this->createLimitation($roleService, $limitation);
427
                $policyCreateStruct->addLimitation($limitationObject);
428
            }
429
        }
430
431
        $roleService->addPolicy($role, $policyCreateStruct);
432
    }
433
}
434