Passed
Push — master ( d6ce84...9c42e9 )
by Gaetano
10:07
created

RoleManager   F

Complexity

Total Complexity 61

Size/Duplication

Total Lines 416
Duplicated Lines 0 %

Test Coverage

Coverage 81.53%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 182
dl 0
loc 416
ccs 128
cts 157
cp 0.8153
rs 3.52
c 2
b 0
f 0
wmc 61

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 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
A load() 0 9 1
B update() 0 53 10
B assignRole() 0 38 10
B generateMigration() 0 69 9
A delete() 0 15 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->validateResultsCount($roleCollection, $step);
69
70
        $this->setReferences($roleCollection, $step);
71
72
        return $roleCollection;
73
    }
74
75
    /**
76 1
     * Method to handle the update operation of the migration instructions
77
     */
78 1
    protected function update($step)
79
    {
80 1
        $roleCollection = $this->matchRoles('update', $step);
81
82
        $this->validateResultsCount($roleCollection, $step);
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->validateResultsCount($roleCollection, $step);
141 1
142
        $this->setReferences($roleCollection, $step);
143
144 1
        $roleService = $this->repository->getRoleService();
145
146
        foreach ($roleCollection as $role) {
147
            $roleService->deleteRole($role);
148
        }
149
150
        return $roleCollection;
151
    }
152 1
153
    /**
154 1
     * @param string $action
155
     * @return RoleCollection
156
     * @throws \Exception
157
     */
158
    protected function matchRoles($action, $step)
159 1
    {
160 1
        if (!isset($step->dsl['name']) && !isset($step->dsl['match'])) {
161
            throw new \Exception("The name of a role or a match condition is required to $action it");
162 1
        }
163
164
        // Backwards compat
165
        if (isset($step->dsl['match'])) {
166 1
            $match = $step->dsl['match'];
167
        } else {
168 1
            $match = array('identifier' => $step->dsl['name']);
169
        }
170
171
        // convert the references passed in the match
172
        $match = $this->resolveReferencesRecursively($match);
173
174
        return $this->roleMatcher->match($match);
175
    }
176
177 1
    /**
178
     * @param Role $role
179 1
     * @param array $references the definitions of the references to set
180
     * @throws \InvalidArgumentException When trying to assign a reference to an unsupported attribute
181 1
     * @return array key: the reference names, values: the reference values
182 1
     */
183 1
    protected function getReferencesValues($role, array $references, $step)
184 1
    {
185 1
        $refs = array();
186 1
187 1
        foreach ($references as $reference) {
188
            switch ($reference['attribute']) {
189 1
                case 'role_id':
190 1
                case 'id':
191
                    $value = $role->id;
192
                    break;
193
                case 'identifier':
194
                case 'role_identifier':
195 1
                    $value = $role->identifier;
196
                    break;
197
                default:
198 1
                    throw new \InvalidArgumentException('Role Manager does not support setting references for attribute ' . $reference['attribute']);
199
            }
200
201
            $refs[$reference['identifier']] = $value;
202
        }
203
204
        return $refs;
205
    }
206
207
    /**
208 5
     * @param array $matchCondition
209
     * @param string $mode
210 5
     * @param array $context
211 5
     * @throws \Exception
212 5
     * @return array
213
     */
214
    public function generateMigration(array $matchCondition, $mode, array $context = array())
215 5
    {
216
        $previousUserId = $this->loginUser($this->getAdminUserIdentifierFromContext($context));
217 5
        $roleCollection = $this->roleMatcher->match($matchCondition);
218 5
        $data = array();
219
220
        /** @var \eZ\Publish\API\Repository\Values\User\Role $role */
221
        foreach ($roleCollection as $role) {
222 5
            $roleData = array(
223 2
                'type' => reset($this->supportedStepTypes),
224 2
                'mode' => $mode
225
            );
226 2
227
            switch ($mode) {
228
                case 'create':
229 2
                    $roleData = array_merge(
230 3
                        $roleData,
231 2
                        array(
232 3
                            'name' => $role->identifier
233 3
                        )
234
                    );
235
                    break;
236 3
                case 'update':
237
                case 'delete':
238
                    $roleData = array_merge(
239
                        $roleData,
240 3
                        array(
241
                            'match' => array(
242
                                RoleMatcher::MATCH_ROLE_IDENTIFIER => $role->identifier
243
                            )
244
                        )
245 5
                    );
246 3
                    break;
247 3
                default:
248 3
                    throw new \Exception("Executor 'role' doesn't support mode '$mode'");
249
            }
250 3
251 3
            if ($mode != 'delete') {
252
                $policies = array();
253
                foreach ($role->getPolicies() as $policy) {
254 3
                    $limitations = array();
255
256
                    foreach ($policy->getLimitations() as $limitation) {
257 3
                        if (!($limitation instanceof Limitation)) {
258 3
                            throw new \Exception("The role contains an invalid limitation for policy {$policy->module}/{$policy->function}, we can not reliably generate its definition.");
259 3
                        }
260 3
                        $limitations[] = $this->limitationConverter->getLimitationArrayWithIdentifiers($limitation);
261
                    }
262
263
                    $policies[] = array(
264 3
                        'module' => $policy->module,
265 3
                        'function' => $policy->function,
266
                        'limitations' => $limitations
267 3
                    );
268
                }
269
270
                $roleData = array_merge(
271
                    $roleData,
272 5
                    array(
273
                        'policies' => $policies
274
                    )
275 5
                );
276 5
            }
277
278
            $data[] = $roleData;
279
        }
280
281
        $this->loginUser($previousUserId);
282
        return $data;
283
    }
284
285
    /**
286
     * @return string[]
287
     */
288
    public function listAllowedConditions()
289
    {
290
        return $this->roleMatcher->listAllowedConditions();
291
    }
292
293
    /**
294
     * Create a new Limitation object based on the type and value in the $limitation array.
295
     *
296
     * <pre>
297
     * $limitation = array(
298
     *  'identifier' => Type of the limitation
299
     *  'values' => array(Values to base the limitation on)
300
     * )
301 1
     * </pre>
302
     *
303 1
     * @param \eZ\Publish\API\Repository\RoleService $roleService
304
     * @param array $limitation
305 1
     * @return Limitation
306
     */
307 1
    protected function createLimitation(RoleService $roleService, array $limitation)
308 1
    {
309
        $limitationType = $roleService->getLimitationType($limitation['identifier']);
310 1
311 1
        // 1st resolve refs (if we got passed a string)
312
        $limitationValue = $this->referenceResolver->resolveReference($limitation['values']);
313
        // then, if we have an array, resolve refs recursively
314
        if (is_array($limitationValue)) {
315
            foreach ($limitationValue as $id => $value) {
316
                $limitationValue[$id] = $this->referenceResolver->resolveReference($value);
317
            }
318
        } else {
319
            // if still a scalar, make sure we can loop over it
320
            $limitationValue = array($limitationValue);
321
        }
322
323
324
        $limitationValue = $this->limitationConverter->resolveLimitationValue($limitation['identifier'], $limitationValue);
325
        return $limitationType->buildValue($limitationValue);
326
    }
327
328
    /**
329
     * Assign a role to users and groups in the assignment array.
330
     *
331
     * <pre>
332 1
     * $assignments = array(
333
     *      array(
334 1
     *          'type' => 'user',
335 1
     *          'ids' => array(user ids),
336 1
     *          'limitation' => array(limitations)
337 1
     *      )
338 1
     * )
339
     * </pre>
340 1
     *
341
     * @param \eZ\Publish\API\Repository\Values\User\Role $role
342 1
     * @param \eZ\Publish\API\Repository\RoleService $roleService
343
     * @param \eZ\Publish\API\Repository\UserService $userService
344
     * @param array $assignments
345 1
     */
346 1
    protected function assignRole(Role $role, RoleService $roleService, UserService $userService, array $assignments)
347 1
    {
348
        foreach ($assignments as $assign) {
349
            switch ($assign['type']) {
350
                case 'user':
351 1
                    foreach ($assign['ids'] as $userId) {
352
                        $userId = $this->referenceResolver->resolveReference($userId);
353
354
                        $user = $userService->loadUser($userId);
355
356
                        if (!isset($assign['limitations'])) {
357
                            $roleService->assignRoleToUser($role, $user);
358
                        } else {
359
                            foreach ($assign['limitations'] as $limitation) {
360
                                $limitationObject = $this->createLimitation($roleService, $limitation);
361
                                $roleService->assignRoleToUser($role, $user, $limitationObject);
362
                            }
363
                        }
364
                    }
365
                    break;
366
                case 'group':
367
                    foreach ($assign['ids'] as $groupId) {
368
                        $groupId = $this->referenceResolver->resolveReference($groupId);
369
370
                        $group = $userService->loadUserGroup($groupId);
371
372
                        if (!isset($assign['limitations'])) {
373 1
                            $roleService->assignRoleToUserGroup($role, $group);
374
                        } else {
375
                            foreach ($assign['limitations'] as $limitation) {
376 1
                                $limitationObject = $this->createLimitation($roleService, $limitation);
377
                                $roleService->assignRoleToUserGroup($role, $group, $limitationObject);
378
                            }
379
                        }
380
                    }
381
                    break;
382
                default:
383
                    throw new \Exception("Unsupported type '{$assign['type']}'");
384
            }
385 1
        }
386
    }
387 1
388
    protected function unassignRole(Role $role, RoleService $roleService, UserService $userService, array $assignments)
389 1
    {
390 1
        foreach ($assignments as $assign) {
391 1
            switch ($assign['type']) {
392 1
                case 'user':
393
                    foreach ($assign['ids'] as $userId) {
394
                        $userId = $this->referenceResolver->resolveReference($userId);
395
396 1
                        $user = $userService->loadUser($userId);
397 1
398
                        $roleService->unassignRoleFromUser($role, $user);
399
                    }
400
                    break;
401
                case 'group':
402
                    foreach ($assign['ids'] as $groupId) {
403
                        $groupId = $this->referenceResolver->resolveReference($groupId);
404
405
                        $group = $userService->loadUserGroup($groupId);
406
                        $roleService->unassignRoleFromUserGroup($role, $group);
407
                    }
408
                    break;
409
                default:
410
                    throw new \Exception("Unsupported type '{$assign['type']}'");
411
            }
412
        }
413
    }
414
415
    /**
416
     * Add new policies to the $role Role.
417
     *
418
     * @param \eZ\Publish\API\Repository\Values\User\Role $role
419
     * @param \eZ\Publish\API\Repository\RoleService $roleService
420
     * @param array $policy
421
     */
422
    protected function addPolicy(Role $role, RoleService $roleService, array $policy)
423
    {
424
        $policyCreateStruct = $roleService->newPolicyCreateStruct($policy['module'], $policy['function']);
425
426
        if (array_key_exists('limitations', $policy)) {
427
            foreach ($policy['limitations'] as $limitation) {
428
                $limitationObject = $this->createLimitation($roleService, $limitation);
429
                $policyCreateStruct->addLimitation($limitationObject);
430
            }
431
        }
432
433
        $roleService->addPolicy($role, $policyCreateStruct);
434
    }
435
}
436