Completed
Push — master ( 09cee1...75eab4 )
by Gaetano
06:21
created

RoleManager::create()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 27
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
dl 0
loc 27
ccs 0
cts 20
cp 0
rs 8.439
c 0
b 0
f 0
cc 5
eloc 14
nc 8
nop 1
crap 30
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
use eZ\Publish\API\Repository\Values\User\Limitation;
13
14
/**
15
 * Handles role migrations.
16
 */
17
class RoleManager extends RepositoryExecutor implements MigrationGeneratorInterface
18
{
19
    protected $supportedStepTypes = array('role');
20
21
    protected $limitationConverter;
22
    protected $roleMatcher;
23
24
    public function __construct(RoleMatcher $roleMatcher, LimitationConverter $limitationConverter)
25
    {
26
        $this->roleMatcher = $roleMatcher;
27
        $this->limitationConverter = $limitationConverter;
28
    }
29
30
    /**
31
     * Method to handle the create operation of the migration instructions
32
     */
33
    protected function create($step)
34
    {
35
        $roleService = $this->repository->getRoleService();
36
        $userService = $this->repository->getUserService();
37
38
        $roleCreateStruct = $roleService->newRoleCreateStruct($step->dsl['name']);
39
40
        // Publish new role
41
        $role = $roleService->createRole($roleCreateStruct);
42
        if (is_callable(array($roleService, 'publishRoleDraft'))) {
43
            $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...
44
        }
45
46
        if (isset($step->dsl['policies'])) {
47
            foreach ($step->dsl['policies'] as $key => $ymlPolicy) {
48
                $this->addPolicy($role, $roleService, $ymlPolicy);
49
            }
50
        }
51
52
        if (isset($step->dsl['assign'])) {
53
            $this->assignRole($role, $roleService, $userService, $step->dsl['assign']);
54
        }
55
56
        $this->setReferences($role, $step);
0 ignored issues
show
Documentation introduced by
$role is of type object<eZ\Publish\API\Re...itory\Values\User\Role>, but the function expects a object<Object>|object<Ka...ctCollectionCollection>.

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...
57
58
        return $role;
59
    }
60
61
    /**
62
     * Method to handle the update operation of the migration instructions
63
     */
64
    protected function update($step)
65
    {
66
        $roleCollection = $this->matchRoles('update', $step);
67
68
        if (count($roleCollection) > 1 && isset($step->dsl['references'])) {
69
            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");
70
        }
71
72
        if (count($roleCollection) > 1 && isset($step->dsl['new_name'])) {
73
            throw new \Exception("Can not execute Role update because multiple roles match, and a new_name is specified in the dsl.");
74
        }
75
76
        $roleService = $this->repository->getRoleService();
77
        $userService = $this->repository->getUserService();
78
79
        /** @var \eZ\Publish\API\Repository\Values\User\Role $role */
80
        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...
81
82
            // Updating role name
83
            if (isset($step->dsl['new_name'])) {
84
                $update = $roleService->newRoleUpdateStruct();
85
                $update->identifier = $step->dsl['new_name'];
86
                $role = $roleService->updateRole($role, $update);
87
            }
88
89
            if (isset($step->dsl['policies'])) {
90
                $ymlPolicies = $step->dsl['policies'];
91
92
                // Removing all policies so we can add them back.
93
                // TODO: Check and update policies instead of remove and add.
94
                $policies = $role->getPolicies();
95
                foreach ($policies as $policy) {
96
                    $roleService->deletePolicy($policy);
97
                }
98
99
                foreach ($ymlPolicies as $ymlPolicy) {
100
                    $this->addPolicy($role, $roleService, $ymlPolicy);
101
                }
102
            }
103
104
            if (isset($step->dsl['assign'])) {
105
                $this->assignRole($role, $roleService, $userService, $step->dsl['assign']);
106
            }
107
108
            $roleCollection[$key] = $role;
109
        }
110
111
        $this->setReferences($roleCollection, $step);
0 ignored issues
show
Documentation introduced by
$roleCollection is of type object<Kaliop\eZMigratio...on\RoleCollection>|null, but the function expects a object<Object>|object<Ka...ctCollectionCollection>.

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...
112
113
        return $roleCollection;
114
    }
115
116
    /**
117
     * Method to handle the delete operation of the migration instructions
118
     */
119
    protected function delete($step)
120
    {
121
        $roleCollection = $this->matchRoles('delete', $step);
122
123
        $this->setReferences($roleCollection, $step);
0 ignored issues
show
Documentation introduced by
$roleCollection is of type object<Kaliop\eZMigratio...on\RoleCollection>|null, but the function expects a object<Object>|object<Ka...ctCollectionCollection>.

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...
124
125
        $roleService = $this->repository->getRoleService();
126
127
        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...
128
            $roleService->deleteRole($role);
129
        }
130
131
        return $roleCollection;
132
    }
133
134
    /**
135
     * @param string $action
136
     * @return RoleCollection
137
     * @throws \Exception
138
     */
139 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...
140
    {
141
        if (!isset($step->dsl['name']) && !isset($step->dsl['match'])) {
142
            throw new \Exception("The name of a role or a match condition is required to $action it");
143
        }
144
145
        // Backwards compat
146
        if (isset($step->dsl['match'])) {
147
            $match = $step->dsl['match'];
148
        } else {
149
            $match = array('identifier' => $step->dsl['name']);
150
        }
151
152
        // convert the references passed in the match
153
        $match = $this->resolveReferencesRecursively($match);
154
155
        return $this->roleMatcher->match($match);
156
    }
157
158
    /**
159
     * @param Role $role
160
     * @param array $references the definitions of the references to set
161
     * @throws \InvalidArgumentException When trying to assign a reference to an unsupported attribute
162
     * @return array key: the reference names, values: the reference values
163
     */
164 View Code Duplication
    protected function getReferencesValues(Role $role, array $references)
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...
165
    {
166
        $refs = array();
167
        foreach ($references as $reference) {
168
            switch ($reference['attribute']) {
169
                case 'role_id':
170
                case 'id':
171
                    $value = $role->id;
172
                    break;
173
                case 'identifier':
174
                case 'role_identifier':
175
                    $value = $role->identifier;
176
                    break;
177
                default:
178
                    throw new \InvalidArgumentException('Role Manager does not support setting references for attribute ' . $reference['attribute']);
179
            }
180
181
            $refs[$reference['identifier']] = $value;
182
        }
183
184
        return $refs;
185
    }
186
187
    /**
188
     * @param array $matchCondition
189
     * @param string $mode
190
     * @param array $context
191
     * @throws \Exception
192
     * @return array
193
     */
194
    public function generateMigration(array $matchCondition, $mode, array $context = array())
195
    {
196
        $previousUserId = $this->loginUser($this->getAdminUserIdentifierFromContext($context));
197
        $roleCollection = $this->roleMatcher->match($matchCondition);
198
        $data = array();
199
200
        /** @var \eZ\Publish\API\Repository\Values\User\Role $role */
201
        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...
202
            $roleData = array(
203
                'type' => reset($this->supportedStepTypes),
204
                'mode' => $mode
205
            );
206
207
            switch ($mode) {
208
                case 'create':
209
                    $roleData = array_merge(
210
                        $roleData,
211
                        array(
212
                            'name' => $role->identifier
213
                        )
214
                    );
215
                    break;
216
                case 'update':
217
                case 'delete':
218
                    $roleData = array_merge(
219
                        $roleData,
220
                        array(
221
                            'match' => array(
222
                                RoleMatcher::MATCH_ROLE_IDENTIFIER => $role->identifier
223
                            )
224
                        )
225
                    );
226
                    break;
227
                default:
228
                    throw new \Exception("Executor 'role' doesn't support mode '$mode'");
229
            }
230
231
            if ($mode != 'delete') {
232
                $policies = array();
233
                foreach ($role->getPolicies() as $policy) {
234
                    $limitations = array();
235
236
                    foreach ($policy->getLimitations() as $limitation) {
237
                        if (!($limitation instanceof Limitation)) {
238
                            throw new \Exception("The role contains an invalid limitation for policy {$policy->module}/{$policy->function}, we can not reliably generate its definition.");
239
                        }
240
                        $limitations[] = $this->limitationConverter->getLimitationArrayWithIdentifiers($limitation);
241
                    }
242
243
                    $policies[] = array(
244
                        'module' => $policy->module,
245
                        'function' => $policy->function,
246
                        'limitations' => $limitations
247
                    );
248
                }
249
250
                $roleData = array_merge(
251
                    $roleData,
252
                    array(
253
                        'policies' => $policies
254
                    )
255
                );
256
            }
257
258
            $data[] = $roleData;
259
        }
260
261
        $this->loginUser($previousUserId);
262
        return $data;
263
    }
264
265
    /**
266
     * Create a new Limitation object based on the type and value in the $limitation array.
267
     *
268
     * <pre>
269
     * $limitation = array(
270
     *  'identifier' => Type of the limitation
271
     *  'values' => array(Values to base the limitation on)
272
     * )
273
     * </pre>
274
     *
275
     * @param \eZ\Publish\API\Repository\RoleService $roleService
276
     * @param array $limitation
277
     * @return Limitation
278
     */
279
    protected function createLimitation(RoleService $roleService, array $limitation)
280
    {
281
        $limitationType = $roleService->getLimitationType($limitation['identifier']);
282
283
        $limitationValue = is_array($limitation['values']) ? $limitation['values'] : array($limitation['values']);
284
285
        foreach ($limitationValue as $id => $value) {
286
            $limitationValue[$id] = $this->referenceResolver->resolveReference($value);
287
        }
288
        $limitationValue = $this->limitationConverter->resolveLimitationValue($limitation['identifier'], $limitationValue);
289
        return $limitationType->buildValue($limitationValue);
290
    }
291
292
    /**
293
     * Assign a role to users and groups in the assignment array.
294
     *
295
     * <pre>
296
     * $assignments = array(
297
     *      array(
298
     *          'type' => 'user',
299
     *          'ids' => array(user ids),
300
     *          'limitation' => array(limitations)
301
     *      )
302
     * )
303
     * </pre>
304
     *
305
     * @param \eZ\Publish\API\Repository\Values\User\Role $role
306
     * @param \eZ\Publish\API\Repository\RoleService $roleService
307
     * @param \eZ\Publish\API\Repository\UserService $userService
308
     * @param array $assignments
309
     */
310
    protected function assignRole(Role $role, RoleService $roleService, UserService $userService, array $assignments)
311
    {
312
        foreach ($assignments as $assign) {
313
            switch ($assign['type']) {
314 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...
315
                    foreach ($assign['ids'] as $userId) {
316
                        $userId = $this->referenceResolver->resolveReference($userId);
317
318
                        $user = $userService->loadUser($userId);
319
320
                        if (!isset($assign['limitations'])) {
321
                            $roleService->assignRoleToUser($role, $user);
322
                        } else {
323
                            foreach ($assign['limitations'] as $limitation) {
324
                                $limitationObject = $this->createLimitation($roleService, $limitation);
325
                                $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...
326
                            }
327
                        }
328
                    }
329
                    break;
330 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...
331
                    foreach ($assign['ids'] as $groupId) {
332
                        $groupId = $this->referenceResolver->resolveReference($groupId);
333
334
                        $group = $userService->loadUserGroup($groupId);
335
336
                        if (!isset($assign['limitations'])) {
337
                            // q: why are we swallowing exceptions here ?
338
                            //try {
339
                                $roleService->assignRoleToUserGroup($role, $group);
340
                            //} 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...
341
                        } else {
342
                            foreach ($assign['limitations'] as $limitation) {
343
                                $limitationObject = $this->createLimitation($roleService, $limitation);
344
                                // q: why are we swallowing exceptions here ?
345
                                //try {
346
                                    $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...
347
                                //} 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...
348
                            }
349
                        }
350
                    }
351
                    break;
352
            }
353
        }
354
    }
355
356
    /**
357
     * Add new policies to the $role Role.
358
     *
359
     * @param \eZ\Publish\API\Repository\Values\User\Role $role
360
     * @param \eZ\Publish\API\Repository\RoleService $roleService
361
     * @param array $policy
362
     */
363
    protected function addPolicy(Role $role, RoleService $roleService, array $policy)
364
    {
365
        $policyCreateStruct = $roleService->newPolicyCreateStruct($policy['module'], $policy['function']);
366
367
        if (array_key_exists('limitations', $policy)) {
368
            foreach ($policy['limitations'] as $limitation) {
369
                $limitationObject = $this->createLimitation($roleService, $limitation);
370
                $policyCreateStruct->addLimitation($limitationObject);
371
            }
372
        }
373
374
        $roleService->addPolicy($role, $policyCreateStruct);
375
    }
376
}
377