Completed
Push — master ( 290d49...5307dc )
by Gaetano
13:54 queued 06:53
created

RoleManager::delete()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 7
cts 7
cp 1
rs 9.7998
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2
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
    protected $supportedActions = array('create', 'load', 'update', 'delete');
21
22
    protected $limitationConverter;
23
    protected $roleMatcher;
24
25 80
    public function __construct(RoleMatcher $roleMatcher, LimitationConverter $limitationConverter)
26
    {
27 80
        $this->roleMatcher = $roleMatcher;
28 80
        $this->limitationConverter = $limitationConverter;
29 80
    }
30
31
    /**
32
     * Method to handle the create operation of the migration instructions
33
     */
34 1
    protected function create($step)
35
    {
36 1
        $roleService = $this->repository->getRoleService();
37 1
        $userService = $this->repository->getUserService();
38
39 1
        $roleName = $this->referenceResolver->resolveReference($step->dsl['name']);
40 1
        $roleCreateStruct = $roleService->newRoleCreateStruct($roleName);
41
42
        // Publish new role
43 1
        $role = $roleService->createRole($roleCreateStruct);
44 1
        if (is_callable(array($roleService, 'publishRoleDraft'))) {
45 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...
46
        }
47
48 1
        if (isset($step->dsl['policies'])) {
49 1
            foreach ($step->dsl['policies'] as $key => $ymlPolicy) {
50 1
                $this->addPolicy($role, $roleService, $ymlPolicy);
51
            }
52
        }
53
54 1
        if (isset($step->dsl['assign'])) {
55
            $this->assignRole($role, $roleService, $userService, $step->dsl['assign']);
56
        }
57
58 1
        $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...ion\AbstractCollection>.

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