RoleManager   F
last analyzed

Complexity

Total Complexity 76

Size/Duplication

Total Lines 487
Duplicated Lines 0 %

Test Coverage

Coverage 79.52%

Importance

Changes 1
Bugs 1 Features 0
Metric Value
eloc 214
dl 0
loc 487
ccs 167
cts 210
cp 0.7952
rs 2.32
c 1
b 1
f 0
wmc 76

16 Methods

Rating   Name   Duplication   Size   Complexity  
A matchRoles() 0 19 5
A getReferencesValues() 0 23 6
A load() 0 9 1
B update() 0 53 10
A delete() 0 15 2
A __construct() 0 4 1
A create() 0 27 5
A listAllowedConditions() 0 3 1
A addPolicy() 0 12 3
A sortPolicyDefinitions() 0 21 5
B assignRole() 0 38 10
B generateMigration() 0 76 9
A unassignRole() 0 23 6
A sortPolicyLimitationsDefinitions() 0 14 6
A createLimitation() 0 19 3
A compareArraysForSorting() 0 10 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\RoleService;
6
use eZ\Publish\API\Repository\UserService;
7
use eZ\Publish\API\Repository\Values\User\Limitation;
8
use eZ\Publish\API\Repository\Values\User\Role;
9
use Kaliop\eZMigrationBundle\API\Collection\RoleCollection;
10
use Kaliop\eZMigrationBundle\API\EnumerableMatcherInterface;
11
use Kaliop\eZMigrationBundle\API\Exception\InvalidStepDefinitionException;
12
use Kaliop\eZMigrationBundle\API\Exception\MigrationBundleException;
13
use Kaliop\eZMigrationBundle\API\MigrationGeneratorInterface;
14
use Kaliop\eZMigrationBundle\Core\Helper\LimitationConverter;
15
use Kaliop\eZMigrationBundle\Core\Matcher\RoleMatcher;
16
17
/**
18
 * Handles role migrations.
19
 */
20
class RoleManager extends RepositoryExecutor implements MigrationGeneratorInterface, EnumerableMatcherInterface
21
{
22
    protected $supportedStepTypes = array('role');
23
    protected $supportedActions = array('create', 'load', 'update', 'delete');
24
25
    protected $limitationConverter;
26
    protected $roleMatcher;
27
28 149
    public function __construct(RoleMatcher $roleMatcher, LimitationConverter $limitationConverter)
29
    {
30 149
        $this->roleMatcher = $roleMatcher;
31 149
        $this->limitationConverter = $limitationConverter;
32 149
    }
33
34
    /**
35
     * Method to handle the create operation of the migration instructions
36
     */
37 1
    protected function create($step)
38
    {
39 1
        $roleService = $this->repository->getRoleService();
40 1
        $userService = $this->repository->getUserService();
41
42 1
        $roleName = $this->resolveReference($step->dsl['name']);
43 1
        $roleCreateStruct = $roleService->newRoleCreateStruct($roleName);
44
45
        // Publish new role
46 1
        $role = $roleService->createRole($roleCreateStruct);
47 1
        if (is_callable(array($roleService, 'publishRoleDraft'))) {
48 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

48
            $roleService->/** @scrutinizer ignore-call */ 
49
                          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...
49
        }
50
51 1
        if (isset($step->dsl['policies'])) {
52 1
            foreach ($step->dsl['policies'] as $key => $ymlPolicy) {
53 1
                $this->addPolicy($role, $roleService, $ymlPolicy);
54
            }
55
        }
56
57 1
        if (isset($step->dsl['assign'])) {
58 1
            $this->assignRole($role, $roleService, $userService, $step->dsl['assign']);
59
        }
60
61 1
        $this->setReferences($role, $step);
62
63 1
        return $role;
64
    }
65
66
    protected function load($step)
67
    {
68
        $roleCollection = $this->matchRoles('load', $step);
69
70
        $this->validateResultsCount($roleCollection, $step);
71
72
        $this->setReferences($roleCollection, $step);
73
74
        return $roleCollection;
75
    }
76
77
    /**
78
     * Method to handle the update operation of the migration instructions
79
     */
80 1
    protected function update($step)
81
    {
82 1
        $roleCollection = $this->matchRoles('update', $step);
83
84 1
        $this->validateResultsCount($roleCollection, $step);
85
86 1
        if (count($roleCollection) > 1 && isset($step->dsl['new_name'])) {
87
            throw new MigrationBundleException("Can not execute Role update because multiple roles match, and a new_name is specified in the dsl.");
88
        }
89
90 1
        $roleService = $this->repository->getRoleService();
91 1
        $userService = $this->repository->getUserService();
92
93
        /** @var \eZ\Publish\API\Repository\Values\User\Role $role */
94 1
        foreach ($roleCollection as $key => $role) {
95
96
            // Updating role name
97 1
            if (isset($step->dsl['new_name'])) {
98
                $update = $roleService->newRoleUpdateStruct();
99
                $newRoleName = $this->resolveReference($step->dsl['new_name']);
100
                $update->identifier = $this->resolveReference($newRoleName);
101
                $role = $roleService->updateRole($role, $update);
102
            }
103
104 1
            if (isset($step->dsl['policies'])) {
105 1
                $ymlPolicies = $step->dsl['policies'];
106
107
                // Removing all policies so we can add them back.
108
                // TODO: Check and update policies instead of remove and add.
109 1
                $policies = $role->getPolicies();
110 1
                foreach ($policies as $policy) {
111
                    $roleService->deletePolicy($policy);
112
                }
113
114 1
                foreach ($ymlPolicies as $ymlPolicy) {
115 1
                    $this->addPolicy($role, $roleService, $ymlPolicy);
116
                }
117
            }
118
119 1
            if (isset($step->dsl['assign'])) {
120 1
                $this->assignRole($role, $roleService, $userService, $step->dsl['assign']);
121
            }
122
123 1
            if (isset($step->dsl['unassign'])) {
124 1
                $this->unassignRole($role, $roleService, $userService, $step->dsl['unassign']);
125
            }
126
127 1
            $roleCollection[$key] = $role;
128
        }
129
130 1
        $this->setReferences($roleCollection, $step);
131
132 1
        return $roleCollection;
133
    }
134
135
    /**
136
     * Method to handle the delete operation of the migration instructions
137
     */
138 1
    protected function delete($step)
139
    {
140 1
        $roleCollection = $this->matchRoles('delete', $step);
141
142 1
        $this->validateResultsCount($roleCollection, $step);
143
144 1
        $this->setReferences($roleCollection, $step);
145
146 1
        $roleService = $this->repository->getRoleService();
147
148 1
        foreach ($roleCollection as $role) {
149 1
            $roleService->deleteRole($role);
150
        }
151
152 1
        return $roleCollection;
153
    }
154
155
    /**
156
     * @param string $action
157
     * @return RoleCollection
158
     * @throws \Exception
159
     */
160 1
    protected function matchRoles($action, $step)
161
    {
162 1
        if (!isset($step->dsl['name']) && !isset($step->dsl['match'])) {
163
            throw new InvalidStepDefinitionException("The name of a role or a match condition is required to $action it");
164
        }
165
166
        // Backwards compat
167 1
        if (isset($step->dsl['match'])) {
168 1
            $match = $step->dsl['match'];
169
        } else {
170 1
            $match = array('identifier' => $step->dsl['name']);
171
        }
172
173
        // convert the references passed in the match
174 1
        $match = $this->resolveReferencesRecursively($match);
175
176 1
        $tolerateMisses = isset($step->dsl['match_tolerate_misses']) ? $this->resolveReference($step->dsl['match_tolerate_misses']) : false;
177
178 1
        return $this->roleMatcher->match($match, $tolerateMisses);
179
    }
180
181
    /**
182
     * @param Role $role
183
     * @param array $references the definitions of the references to set
184
     * @throws InvalidStepDefinitionException
185
     * @return array key: the reference names, values: the reference values
186
     */
187 1
    protected function getReferencesValues($role, array $references, $step)
188
    {
189 1
        $refs = array();
190
191 1
        foreach ($references as $key => $reference) {
192 1
            $reference = $this->parseReferenceDefinition($key, $reference);
193 1
            switch ($reference['attribute']) {
194 1
                case 'role_id':
195 1
                case 'id':
196 1
                    $value = $role->id;
197 1
                    break;
198 1
                case 'identifier':
199
                case 'role_identifier':
200 1
                    $value = $role->identifier;
201 1
                    break;
202
                default:
203
                    throw new InvalidStepDefinitionException('Role Manager does not support setting references for attribute ' . $reference['attribute']);
204
            }
205
206 1
            $refs[$reference['identifier']] = $value;
207
        }
208
209 1
        return $refs;
210
    }
211
212
    /**
213
     * @param array $matchConditions
214
     * @param string $mode
215
     * @param array $context
216
     * @return array
217
     * @throws \Exception
218
     */
219 5
    public function generateMigration(array $matchConditions, $mode, array $context = array())
220
    {
221 5
        $data = array();
222 5
        $previousUserId = $this->loginUser($this->getAdminUserIdentifierFromContext($context));
223 5
        try {
224
            $roleCollection = $this->roleMatcher->match($matchConditions);
225
226 5
            /** @var \eZ\Publish\API\Repository\Values\User\Role $role */
227
            foreach ($roleCollection as $role) {
228 5
                $roleData = array(
229 5
                    'type' => reset($this->supportedStepTypes),
230
                    'mode' => $mode
231
                );
232 5
233 5
                switch ($mode) {
234 2
                    case 'create':
235 2
                        $roleData = array_merge(
236
                            $roleData,
237 2
                            array(
238
                                'name' => $role->identifier
239
                            )
240 2
                        );
241 3
                        break;
242 2
                    case 'update':
243 3
                    case 'delete':
244 3
                        $roleData = array_merge(
245
                            $roleData,
246
                            array(
247 3
                                'match' => array(
248
                                    RoleMatcher::MATCH_ROLE_IDENTIFIER => $role->identifier
249
                                )
250
                            )
251 3
                        );
252
                        break;
253
                    default:
254
                        throw new InvalidStepDefinitionException("Executor 'role' doesn't support mode '$mode'");
255
                }
256 5
257 3
                if ($mode != 'delete') {
258 3
                    $policies = array();
259 3
                    foreach ($role->getPolicies() as $policy) {
260
                        $limitations = array();
261 3
262 3
                        foreach ($policy->getLimitations() as $limitation) {
263
                            if (!($limitation instanceof Limitation)) {
264
                                throw new MigrationBundleException("The role contains an invalid limitation for policy {$policy->module}/{$policy->function}, we can not reliably generate its definition.");
265 3
                            }
266
                            $limitations[] = $this->limitationConverter->getLimitationArrayWithIdentifiers($limitation);
267
                        }
268 3
                        // try to sort predictably to ease diffing
269
                        $this->sortPolicyLimitationsDefinitions($limitations);
270 3
271 3
                        $policies[] = array(
272 3
                            'module' => $policy->module,
273 3
                            'function' => $policy->function,
274
                            'limitations' => $limitations
275
                        );
276
                    }
277 3
                    // try to sort predictably to ease diffing
278
                    $this->sortPolicyDefinitions($policies);
279 3
280 3
                    $roleData = array_merge(
281
                        $roleData,
282 3
                        array(
283
                            'policies' => $policies
284
                        )
285
                    );
286
                }
287 5
288
                $data[] = $roleData;
289
            }
290 5
        } finally {
291 5
            $this->loginUser($previousUserId);
292
        }
293
294
        return $data;
295
    }
296
297
    /**
298
     * @return string[]
299
     */
300
    public function listAllowedConditions()
301
    {
302
        return $this->roleMatcher->listAllowedConditions();
303
    }
304
305
    /**
306
     * Create a new Limitation object based on the type and value in the $limitation array.
307
     *
308
     * <pre>
309
     * $limitation = array(
310
     *  'identifier' => Type of the limitation
311
     *  'values' => array(Values to base the limitation on)
312
     * )
313
     * </pre>
314
     *
315
     * @param \eZ\Publish\API\Repository\RoleService $roleService
316 1
     * @param array $limitation
317
     * @return Limitation
318 1
     */
319
    protected function createLimitation(RoleService $roleService, array $limitation)
320
    {
321 1
        $limitationType = $roleService->getLimitationType($limitation['identifier']);
322
323 1
        // 1st resolve refs (if we got passed a string)
324 1
        $limitationValue = $this->resolveReference($limitation['values']);
325 1
        // then, if we have an array, resolve refs recursively
326
        if (is_array($limitationValue)) {
327
            foreach ($limitationValue as $id => $value) {
328
                $limitationValue[$id] = $this->resolveReference($value);
329
            }
330
        } else {
331
            // if still a scalar, make sure we can loop over it
332
            $limitationValue = array($limitationValue);
333 1
        }
334 1
335
336
        $limitationValue = $this->limitationConverter->resolveLimitationValue($limitation['identifier'], $limitationValue);
337
        return $limitationType->buildValue($limitationValue);
338
    }
339
340
    /**
341
     * Assign a role to users and groups in the assignment array.
342
     *
343
     * <pre>
344
     * $assignments = array(
345
     *      array(
346
     *          'type' => 'user',
347
     *          'ids' => array(user ids),
348
     *          'limitation' => array(limitations)
349
     *      )
350
     * )
351
     * </pre>
352
     *
353
     * @param \eZ\Publish\API\Repository\Values\User\Role $role
354
     * @param \eZ\Publish\API\Repository\RoleService $roleService
355 1
     * @param \eZ\Publish\API\Repository\UserService $userService
356
     * @param array $assignments
357 1
     */
358 1
    protected function assignRole(Role $role, RoleService $roleService, UserService $userService, array $assignments)
359 1
    {
360 1
        foreach ($assignments as $assign) {
361 1
            switch ($assign['type']) {
362
                case 'user':
363 1
                    foreach ($assign['ids'] as $userId) {
364
                        $userId = $this->resolveReference($userId);
365 1
366
                        $user = $userService->loadUser($userId);
367
368 1
                        if (!isset($assign['limitations'])) {
369 1
                            $roleService->assignRoleToUser($role, $user);
370 1
                        } else {
371
                            foreach ($assign['limitations'] as $limitation) {
372
                                $limitationObject = $this->createLimitation($roleService, $limitation);
373
                                $roleService->assignRoleToUser($role, $user, $limitationObject);
374 1
                            }
375 1
                        }
376 1
                    }
377 1
                    break;
378
                case 'group':
379 1
                    foreach ($assign['ids'] as $groupId) {
380
                        $groupId = $this->resolveReference($groupId);
381 1
382 1
                        $group = $userService->loadUserGroup($groupId);
383
384
                        if (!isset($assign['limitations'])) {
385
                            $roleService->assignRoleToUserGroup($role, $group);
386
                        } else {
387
                            foreach ($assign['limitations'] as $limitation) {
388
                                $limitationObject = $this->createLimitation($roleService, $limitation);
389
                                $roleService->assignRoleToUserGroup($role, $group, $limitationObject);
390 1
                            }
391
                        }
392
                    }
393
                    break;
394
                default:
395 1
                    throw new InvalidStepDefinitionException("Unsupported type '{$assign['type']}'");
396
            }
397 1
        }
398
    }
399 1
400 1
    protected function unassignRole(Role $role, RoleService $roleService, UserService $userService, array $assignments)
401 1
    {
402
        foreach ($assignments as $assign) {
403
            switch ($assign['type']) {
404
                case 'user':
405
                    foreach ($assign['ids'] as $userId) {
406
                        $userId = $this->resolveReference($userId);
407
408
                        $user = $userService->loadUser($userId);
409
410 1
                        $roleService->unassignRoleFromUser($role, $user);
411 1
                    }
412 1
                    break;
413
                case 'group':
414 1
                    foreach ($assign['ids'] as $groupId) {
415 1
                        $groupId = $this->resolveReference($groupId);
416
417 1
                        $group = $userService->loadUserGroup($groupId);
418
                        $roleService->unassignRoleFromUserGroup($role, $group);
419
                    }
420
                    break;
421
                default:
422 1
                    throw new InvalidStepDefinitionException("Unsupported type '{$assign['type']}'");
423
            }
424
        }
425
    }
426
427
    /**
428
     * Add new policies to the $role Role.
429
     *
430
     * @param \eZ\Publish\API\Repository\Values\User\Role $role
431 1
     * @param \eZ\Publish\API\Repository\RoleService $roleService
432
     * @param array $policy
433 1
     */
434
    protected function addPolicy(Role $role, RoleService $roleService, array $policy)
435 1
    {
436 1
        $policyCreateStruct = $roleService->newPolicyCreateStruct($policy['module'], $policy['function']);
437 1
438 1
        if (array_key_exists('limitations', $policy)) {
439
            foreach ($policy['limitations'] as $limitation) {
440
                $limitationObject = $this->createLimitation($roleService, $limitation);
441
                $policyCreateStruct->addLimitation($limitationObject);
442 1
            }
443 1
        }
444
445 3
        $roleService->addPolicy($role, $policyCreateStruct);
446
    }
447
448 1
    protected function sortPolicyLimitationsDefinitions(array &$limitations)
449 1
    {
450
        usort($limitations, function($l1, $l2) {
451
            if (($iComp = strcmp($l1['identifier'], $l2['identifier'])) != 0 ) {
452
                return $iComp;
453
            }
454
            if (is_int($l1['values']) || is_float($l1['values'])) {
455
                return $l1['values'] - $l2['values'];
456
            }
457
            if (is_string($l1['values'])) {
458
                return strcmp($l1['values'], $l2['values']);
459
            }
460 3
            if (is_array($l1['values'])) {
461 3
                return $this->compareArraysForSorting($l1['values'], $l2['values']);
462
            }
463 3
        });
464
    }
465
466
    protected function sortPolicyDefinitions(array &$policies)
467 3
    {
468 3
        // try to sort predictably to ease diffing
469
        usort($policies, function($p1, $p2) {
470 3
            if (($mComp = strcmp($p1['module'], $p2['module'])) != 0) {
471 3
                return $mComp;
472
            }
473
            if (($fComp = strcmp($p1['function'], $p2['function'])) != 0) {
474
                return $fComp;
475
            }
476
            // ugly: sort by comparing limitations identifiers
477
            /// @todo if limitations identifiers are the same, sort by lim. values...
478
            $p1LimIds = array();
479
            $p2LimIds = array();
480
            foreach ($p1['limitations'] as $lim) {
481
                $p1LimIds[] = $lim['identifier'];
482
            }
483
            foreach ($p2['limitations'] as $lim) {
484
                $p2LimIds[] = $lim['identifier'];
485 3
            }
486 3
            return $this->compareArraysForSorting($p1LimIds, $p2LimIds);
487
        });
488
    }
489
490
    /**
491
     * Comparison function useful when sorting arrays based only on their values
492
     * @param string[] $a1
493
     * @param string[] $a2
494
     * @return bool|int
495
     * @todo allow sorting properly int[] and float[] arrays
496
     */
497
    protected function compareArraysForSorting($a1, $a2)
498
    {
499
        $len = min(count($a1), count($a2));
500
        for ($i = 0; $i < $len; $i++) {
501
            if ($cmp = strcmp($a1[$i], $a2[$i]) != 0) {
502
                return $cmp;
503
            }
504
        }
505
        // the array with fewer elements wins
506
        return count($a1) - count($a2);
507
    }
508
}
509