Completed
Push — master ( 68da2d...d119ba )
by Gaetano
07:21
created

RoleManager::assignRole()   D

Complexity

Conditions 10
Paths 8

Size

Total Lines 45
Code Lines 25

Duplication

Lines 38
Ratio 84.44 %

Code Coverage

Tests 0
CRAP Score 110

Importance

Changes 0
Metric Value
dl 38
loc 45
ccs 0
cts 0
cp 0
rs 4.8196
c 0
b 0
f 0
cc 10
eloc 25
nc 8
nop 4
crap 110

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Core\Helper\LimitationConverter;
11
use Kaliop\eZMigrationBundle\Core\Matcher\RoleMatcher;
12
13
/**
14
 * Handles role migrations.
15
 */
16
class RoleManager extends RepositoryExecutor implements MigrationGeneratorInterface
17
{
18
    protected $supportedStepTypes = array('role');
19
20 20
    protected $limitationConverter;
21
    protected $roleMatcher;
22 20
23 20
    public function __construct(RoleMatcher $roleMatcher, LimitationConverter $limitationConverter)
24
    {
25
        $this->roleMatcher = $roleMatcher;
26
        $this->limitationConverter = $limitationConverter;
27
    }
28 1
29
    /**
30 1
     * Method to handle the create operation of the migration instructions
31 1
     */
32
    protected function create($step)
33 1
    {
34
        $roleService = $this->repository->getRoleService();
35
        $userService = $this->repository->getUserService();
36 1
37
        $roleCreateStruct = $roleService->newRoleCreateStruct($step->dsl['name']);
38 1
39 1
        // Publish new role
40 1
        $role = $roleService->createRole($roleCreateStruct);
41 1
        if (is_callable(array($roleService, 'publishRoleDraft'))) {
42 1
            $roleService->publishRoleDraft($role);
0 ignored issues
show
Bug introduced by
The method publishRoleDraft() does not seem to exist on object<eZ\Publish\API\Repository\RoleService>.

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...
43 1
        }
44
45 1
        if (isset($step->dsl['policies'])) {
46
            foreach ($step->dsl['policies'] as $key => $ymlPolicy) {
47
                $this->addPolicy($role, $roleService, $ymlPolicy);
48
            }
49 1
        }
50 1
51
        if (isset($step->dsl['assign'])) {
52
            $this->assignRole($role, $roleService, $userService, $step->dsl['assign']);
53
        }
54
55 1
        $this->setReferences($role, $step);
56
57 1
        return $role;
58 1
    }
59
60 1
    /**
61
     * Method to handle the update operation of the migration instructions
62 1
     */
63
    protected function update($step)
64
    {
65 1
        $roleCollection = $this->matchRoles('update', $step);
66
67
        if (count($roleCollection) > 1 && isset($step->dsl['references'])) {
68
            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");
69
        }
70
71 1
        if (count($roleCollection) > 1 && isset($step->dsl['new_name'])) {
72 1
            throw new \Exception("Can not execute Role update because multiple roles match, and a new_name is specified in the dsl.");
73
        }
74
75
        $roleService = $this->repository->getRoleService();
76 1
        $userService = $this->repository->getUserService();
77 1
78
        /** @var \eZ\Publish\API\Repository\Values\User\Role $role */
79 1
        foreach ($roleCollection as $key => $role) {
0 ignored issues
show
Bug introduced by
The expression $roleCollection of type object<Kaliop\eZMigratio...on\RoleCollection>|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
80
81 1
            // Updating role name
82 1
            if (isset($step->dsl['new_name'])) {
83 1
                $update = $roleService->newRoleUpdateStruct();
84 1
                $update->identifier = $step->dsl['new_name'];
85
                $role = $roleService->updateRole($role, $update);
86 1
            }
87 1
88 1
            if (isset($step->dsl['policies'])) {
89
                $ymlPolicies = $step->dsl['policies'];
90 1
91 1
                // Removing all policies so we can add them back.
92
                // TODO: Check and update policies instead of remove and add.
93 1
                $policies = $role->getPolicies();
94
                foreach ($policies as $policy) {
95
                    $roleService->deletePolicy($policy);
96
                }
97
98 1
                foreach ($ymlPolicies as $ymlPolicy) {
99
                    $this->addPolicy($role, $roleService, $ymlPolicy);
100
                }
101 1
            }
102
103 1
            if (isset($step->dsl['assign'])) {
104 1
                $this->assignRole($role, $roleService, $userService, $step->dsl['assign']);
105 1
            }
106 1
107 1
            $roleCollection[$key] = $role;
108
        }
109
110
        $this->setReferences($roleCollection, $step);
0 ignored issues
show
Bug introduced by
It seems like $roleCollection defined by $this->matchRoles('update', $step) on line 65 can be null; however, Kaliop\eZMigrationBundle...anager::setReferences() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
111
112
        return $roleCollection;
113
    }
114
115
    /**
116
     * Method to handle the delete operation of the migration instructions
117
     */
118 1
    protected function delete($step)
119
    {
120 1
        $roleCollection = $this->matchRoles('delete', $step);
121 1
122
        $this->setReferences($roleCollection, $step);
0 ignored issues
show
Bug introduced by
It seems like $roleCollection defined by $this->matchRoles('delete', $step) on line 120 can be null; however, Kaliop\eZMigrationBundle...anager::setReferences() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
123
124 1
        $roleService = $this->repository->getRoleService();
125 1
126 1
        foreach ($roleCollection as $role) {
0 ignored issues
show
Bug introduced by
The expression $roleCollection of type object<Kaliop\eZMigratio...on\RoleCollection>|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
127 1
            $roleService->deleteRole($role);
128 1
        }
129 1
130 1
        return $roleCollection;
131 1
    }
132 1
133 1
    /**
134
     * @param string $action
135 1
     * @return RoleCollection
136 1
     * @throws \Exception
137
     */
138 1 View Code Duplication
    protected function matchRoles($action, $step)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
139 1
    {
140
        if (!isset($step->dsl['name']) && !isset($step->dsl['match'])) {
141 1
            throw new \Exception("The name of a role or a match condition is required to $action it");
142
        }
143
144
        // Backwards compat
145
        if (isset($step->dsl['match'])) {
146
            $match = $step->dsl['match'];
147
        } else {
148
            $match = array('identifier' => $step->dsl['name']);
149
        }
150
151
        // convert the references passed in the match
152
        $match = $this->resolveReferencesRecursively($match);
0 ignored issues
show
Deprecated Code introduced by
The method Kaliop\eZMigrationBundle...ReferencesRecursively() has been deprecated with message: will be moved into the reference resolver classes

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
153
154
        return $this->roleMatcher->match($match);
155
    }
156
157
    /**
158 1
     * Set references to object attributes to be retrieved later.
159
     *
160 1
     * The Role Manager currently support setting references to role_ids.
161
     *
162 1
     * @param \eZ\Publish\API\Repository\Values\User\Role|RoleCollection $role
163
     * @throws \InvalidArgumentException When trying to assign a reference to an unsupported attribute
164 1
     * @return boolean
165 1
     */
166 View Code Duplication
    protected function setReferences($role, $step)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
167
    {
168
        if (!array_key_exists('references', $step->dsl)) {
169 1
            return false;
170 1
        }
171 1
172
        $this->setReferencesCommon($role, $step);
173
        $role = $this->insureSingleEntity($role, $step);
174
175
        foreach ($step->dsl['references'] as $reference) {
176
            switch ($reference['attribute']) {
177
                case 'role_id':
178
                case 'id':
179
                    $value = $role->id;
180
                    break;
181
                case 'identifier':
182
                case 'role_identifier':
183
                    $value = $role->identifier;
184
                    break;
185
                default:
186
                    throw new \InvalidArgumentException('Role Manager does not support setting references for attribute ' . $reference['attribute']);
187
            }
188
189
            $overwrite = false;
190
            if (isset($reference['overwrite'])) {
191
                $overwrite = $reference['overwrite'];
192 1
            }
193
            $this->referenceResolver->addReference($reference['identifier'], $value, $overwrite);
194 1
        }
195 1
196 1
        return true;
197 1
    }
198 1
199
    /**
200 1
     * @param array $matchCondition
201
     * @param string $mode
202
     * @param array $context
203 1
     * @throws \Exception
204 1
     * @return array
205 1
     */
206 1
    public function generateMigration(array $matchCondition, $mode, array $context = array())
207
    {
208 1
        $previousUserId = $this->loginUser($this->getAdminUserIdentifierFromContext($context));
209 1
        $roleCollection = $this->roleMatcher->match($matchCondition);
210
        $data = array();
211
212
        /** @var \eZ\Publish\API\Repository\Values\User\Role $role */
213
        foreach ($roleCollection as $role) {
0 ignored issues
show
Bug introduced by
The expression $roleCollection of type object<Kaliop\eZMigratio...on\RoleCollection>|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
214
            $roleData = array(
215
                'type' => reset($this->supportedStepTypes),
216
                'mode' => $mode
217
            );
218
219
            switch ($mode) {
220
                case 'create':
221
                    $roleData = array_merge(
222
                        $roleData,
223
                        array(
224
                            'name' => $role->identifier
225
                        )
226
                    );
227
                    break;
228 1
                case 'update':
229 1
                case 'delete':
230 1
                    $roleData = array_merge(
231
                        $roleData,
232
                        array(
233
                            'match' => array(
234
                                RoleMatcher::MATCH_ROLE_IDENTIFIER => $role->identifier
235
                            )
236
                        )
237
                    );
238
                    break;
239
                default:
240
                    throw new \Exception("Executor 'role' doesn't support mode '$mode'");
241
            }
242
243
            if ($mode != 'delete') {
244
                $policies = array();
245
                foreach ($role->getPolicies() as $policy) {
246
                    $limitations = array();
247
248
                    foreach ($policy->getLimitations() as $limitation) {
249
                        $limitations[] = $this->limitationConverter->getLimitationArrayWithIdentifiers($limitation);
250
                    }
251
252
                    $policies[] = array(
253
                        'module' => $policy->module,
254
                        'function' => $policy->function,
255
                        'limitations' => $limitations
256
                    );
257
                }
258
259
                $roleData = array_merge(
260
                    $roleData,
261
                    array(
262
                        'policies' => $policies
263
                    )
264
                );
265
            }
266
267
            $data[] = $roleData;
268
        }
269
270
        $this->loginUser($previousUserId);
271
        return $data;
272
    }
273
274
    /**
275
     * Create a new Limitation object based on the type and value in the $limitation array.
276
     *
277
     * <pre>
278
     * $limitation = array(
279 1
     *  'identifier' => Type of the limitation
280
     *  'values' => array(Values to base the limitation on)
281 1
     * )
282
     * </pre>
283 1
     *
284
     * @param \eZ\Publish\API\Repository\RoleService $roleService
285
     * @param array $limitation
286
     * @return \eZ\Publish\API\Repository\Values\User\Limitation
287
     */
288
    protected function createLimitation(RoleService $roleService, array $limitation)
289
    {
290 1
        $limitationType = $roleService->getLimitationType($limitation['identifier']);
291 1
292
        $limitationValue = is_array($limitation['values']) ? $limitation['values'] : array($limitation['values']);
293
294
        foreach ($limitationValue as $id => $value) {
295
            $limitationValue[$id] = $this->referenceResolver->resolveReference($value);
296
        }
297
        $limitationValue = $this->limitationConverter->resolveLimitationValue($limitation['identifier'], $limitationValue);
298
        return $limitationType->buildValue($limitationValue);
299
    }
300
301
    /**
302
     * Assign a role to users and groups in the assignment array.
303
     *
304
     * <pre>
305
     * $assignments = array(
306
     *      array(
307
     *          'type' => 'user',
308
     *          'ids' => array(user ids),
309
     *          'limitation' => array(limitations)
310
     *      )
311
     * )
312
     * </pre>
313
     *
314
     * @param \eZ\Publish\API\Repository\Values\User\Role $role
315
     * @param \eZ\Publish\API\Repository\RoleService $roleService
316
     * @param \eZ\Publish\API\Repository\UserService $userService
317
     * @param array $assignments
318
     */
319
    protected function assignRole(Role $role, RoleService $roleService, UserService $userService, array $assignments)
320
    {
321
        foreach ($assignments as $assign) {
322
            switch ($assign['type']) {
323 View Code Duplication
                case 'user':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
324
                    foreach ($assign['ids'] as $userId) {
325
                        $userId = $this->referenceResolver->resolveReference($userId);
326
327
                        $user = $userService->loadUser($userId);
328
329
                        if (!isset($assign['limitations'])) {
330
                            $roleService->assignRoleToUser($role, $user);
331
                        } else {
332
                            foreach ($assign['limitations'] as $limitation) {
333
                                $limitationObject = $this->createLimitation($roleService, $limitation);
334
                                $roleService->assignRoleToUser($role, $user, $limitationObject);
0 ignored issues
show
Documentation introduced by
$limitationObject is of type object<eZ\Publish\API\Re...Values\User\Limitation>, but the function expects a null|object<eZ\Publish\A...itation\RoleLimitation>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
335
                            }
336
                        }
337
                    }
338
                    break;
339 View Code Duplication
                case 'group':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
340
                    foreach ($assign['ids'] as $groupId) {
341
                        $groupId = $this->referenceResolver->resolveReference($groupId);
342
343
                        $group = $userService->loadUserGroup($groupId);
344
345
                        if (!isset($assign['limitations'])) {
346
                            // q: why are we swallowing exceptions here ?
347
                            //try {
348
                                $roleService->assignRoleToUserGroup($role, $group);
349
                            //} catch (InvalidArgumentException $e) {}
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
350
                        } else {
351
                            foreach ($assign['limitations'] as $limitation) {
352
                                $limitationObject = $this->createLimitation($roleService, $limitation);
353
                                // q: why are we swallowing exceptions here ?
354
                                //try {
355
                                    $roleService->assignRoleToUserGroup($role, $group, $limitationObject);
0 ignored issues
show
Documentation introduced by
$limitationObject is of type object<eZ\Publish\API\Re...Values\User\Limitation>, but the function expects a null|object<eZ\Publish\A...itation\RoleLimitation>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
356
                                //} catch (InvalidArgumentException $e) {}
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
357
                            }
358
                        }
359
                    }
360
                    break;
361
            }
362
        }
363
    }
364
365
    /**
366
     * Add new policies to the $role Role.
367
     *
368
     * @param \eZ\Publish\API\Repository\Values\User\Role $role
369
     * @param \eZ\Publish\API\Repository\RoleService $roleService
370
     * @param array $policy
371
     */
372
    protected function addPolicy(Role $role, RoleService $roleService, array $policy)
373
    {
374
        $policyCreateStruct = $roleService->newPolicyCreateStruct($policy['module'], $policy['function']);
375
376
        if (array_key_exists('limitations', $policy)) {
377
            foreach ($policy['limitations'] as $limitation) {
378
                $limitationObject = $this->createLimitation($roleService, $limitation);
379
                $policyCreateStruct->addLimitation($limitationObject);
380
            }
381
        }
382
383
        $roleService->addPolicy($role, $policyCreateStruct);
384
    }
385
}
386