Completed
Push — 6.3 ( 27f2b3...4e06bb )
by
unknown
20:25
created

UserService   F

Complexity

Total Complexity 134

Size/Duplication

Total Lines 1149
Duplicated Lines 6.01 %

Coupling/Cohesion

Components 1
Dependencies 46

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 69
loc 1149
wmc 134
lcom 1
cbo 46
rs 0.5217

28 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 13 1
B createUserGroup() 0 33 4
A loadUserGroup() 0 6 1
B loadSubUserGroups() 7 39 5
A searchSubGroups() 0 21 2
A deleteUserGroup() 14 14 2
B moveUserGroup() 0 31 4
B updateUserGroup() 10 41 6
F createUser() 3 125 22
B loadUser() 0 30 4
A loadAnonymousUser() 0 4 1
B loadUserByCredentials() 0 27 5
A loadUserByLogin() 0 10 3
A loadUsersByEmail() 0 13 4
A deleteUser() 14 14 2
D updateUser() 14 97 23
B assignUserToUserGroup() 0 37 5
C unAssignUserFromUserGroup() 0 37 7
B loadUserGroupsOfUser() 7 45 5
B loadUsersOfUserGroup() 0 43 3
A newUserCreateStruct() 0 20 2
A newUserGroupCreateStruct() 0 16 2
A newUserUpdateStruct() 0 4 1
A newUserGroupUpdateStruct() 0 4 1
A buildDomainUserGroupObject() 0 22 3
A buildDomainUserObject() 0 18 2
B createPasswordHash() 0 19 5
D getSortClauseBySortField() 0 44 9

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like UserService 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 UserService, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * File containing the eZ\Publish\Core\Repository\UserService class.
5
 *
6
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
7
 * @license For full copyright and license information view LICENSE file distributed with this source code.
8
 *
9
 * @version //autogentag//
10
 */
11
namespace eZ\Publish\Core\Repository;
12
13
use eZ\Publish\API\Repository\Values\Content\LocationQuery;
14
use eZ\Publish\Core\Repository\Values\User\UserCreateStruct;
15
use eZ\Publish\API\Repository\Values\User\UserCreateStruct as APIUserCreateStruct;
16
use eZ\Publish\API\Repository\Values\User\UserUpdateStruct;
17
use eZ\Publish\Core\Repository\Values\User\User;
18
use eZ\Publish\API\Repository\Values\User\User as APIUser;
19
use eZ\Publish\Core\Repository\Values\User\UserGroup;
20
use eZ\Publish\API\Repository\Values\User\UserGroup as APIUserGroup;
21
use eZ\Publish\Core\Repository\Values\User\UserGroupCreateStruct;
22
use eZ\Publish\API\Repository\Values\User\UserGroupCreateStruct as APIUserGroupCreateStruct;
23
use eZ\Publish\API\Repository\Values\User\UserGroupUpdateStruct;
24
use eZ\Publish\API\Repository\Values\Content\Location;
25
use eZ\Publish\API\Repository\Values\Content\Content as APIContent;
26
use eZ\Publish\SPI\Persistence\User\Handler;
27
use eZ\Publish\API\Repository\Repository as RepositoryInterface;
28
use eZ\Publish\API\Repository\UserService as UserServiceInterface;
29
use eZ\Publish\SPI\Persistence\User as SPIUser;
30
use eZ\Publish\Core\FieldType\User\Value as UserValue;
31
use eZ\Publish\API\Repository\Values\Content\Query\SortClause;
32
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\LogicalAnd as CriterionLogicalAnd;
33
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\ContentTypeId as CriterionContentTypeId;
34
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\LocationId as CriterionLocationId;
35
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\ParentLocationId as CriterionParentLocationId;
36
use eZ\Publish\Core\Base\Exceptions\ContentValidationException;
37
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentValue;
38
use eZ\Publish\Core\Base\Exceptions\BadStateException;
39
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException;
40
use eZ\Publish\Core\Base\Exceptions\NotFoundException;
41
use eZ\Publish\Core\Base\Exceptions\UnauthorizedException;
42
use ezcMailTools;
43
use Exception;
44
45
/**
46
 * This service provides methods for managing users and user groups.
47
 *
48
 * @example Examples/user.php
49
 */
50
class UserService implements UserServiceInterface
51
{
52
    /**
53
     * @var \eZ\Publish\API\Repository\Repository
54
     */
55
    protected $repository;
56
57
    /**
58
     * @var \eZ\Publish\SPI\Persistence\User\Handler
59
     */
60
    protected $userHandler;
61
62
    /**
63
     * @var array
64
     */
65
    protected $settings;
66
67
    /**
68
     * Setups service with reference to repository object that created it & corresponding handler.
69
     *
70
     * @param \eZ\Publish\API\Repository\Repository $repository
71
     * @param \eZ\Publish\SPI\Persistence\User\Handler $userHandler
72
     * @param array $settings
73
     */
74
    public function __construct(RepositoryInterface $repository, Handler $userHandler, array $settings = array())
75
    {
76
        $this->repository = $repository;
77
        $this->userHandler = $userHandler;
78
        // Union makes sure default settings are ignored if provided in argument
79
        $this->settings = $settings + array(
80
            'defaultUserPlacement' => 12,
81
            'userClassID' => 4,// @todo Rename this settings to swap out "Class" for "Type"
82
            'userGroupClassID' => 3,
83
            'hashType' => User::PASSWORD_HASH_MD5_USER,
84
            'siteName' => 'ez.no',
85
        );
86
    }
87
88
    /**
89
     * Creates a new user group using the data provided in the ContentCreateStruct parameter.
90
     *
91
     * In 4.x in the content type parameter in the profile is ignored
92
     * - the content type is determined via configuration and can be set to null.
93
     * The returned version is published.
94
     *
95
     * @param \eZ\Publish\API\Repository\Values\User\UserGroupCreateStruct $userGroupCreateStruct a structure for setting all necessary data to create this user group
96
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $parentGroup
97
     *
98
     * @return \eZ\Publish\API\Repository\Values\User\UserGroup
99
     *
100
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to create a user group
101
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the input structure has invalid data
102
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $userGroupCreateStruct is not valid
103
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException if a required field is missing or set to an empty value
104
     */
105
    public function createUserGroup(APIUserGroupCreateStruct $userGroupCreateStruct, APIUserGroup $parentGroup)
106
    {
107
        $contentService = $this->repository->getContentService();
108
        $locationService = $this->repository->getLocationService();
109
        $contentTypeService = $this->repository->getContentTypeService();
110
111
        if ($userGroupCreateStruct->contentType === null) {
112
            $userGroupContentType = $contentTypeService->loadContentType($this->settings['userGroupClassID']);
113
            $userGroupCreateStruct->contentType = $userGroupContentType;
114
        }
115
116
        $loadedParentGroup = $this->loadUserGroup($parentGroup->id);
117
118
        if ($loadedParentGroup->getVersionInfo()->getContentInfo()->mainLocationId === null) {
119
            throw new InvalidArgumentException('parentGroup', 'parent user group has no main location');
120
        }
121
122
        $locationCreateStruct = $locationService->newLocationCreateStruct(
123
            $loadedParentGroup->getVersionInfo()->getContentInfo()->mainLocationId
124
        );
125
126
        $this->repository->beginTransaction();
127
        try {
128
            $contentDraft = $contentService->createContent($userGroupCreateStruct, array($locationCreateStruct));
129
            $publishedContent = $contentService->publishVersion($contentDraft->getVersionInfo());
130
            $this->repository->commit();
131
        } catch (Exception $e) {
132
            $this->repository->rollback();
133
            throw $e;
134
        }
135
136
        return $this->buildDomainUserGroupObject($publishedContent);
137
    }
138
139
    /**
140
     * Loads a user group for the given id.
141
     *
142
     * @param mixed $id
143
     *
144
     * @return \eZ\Publish\API\Repository\Values\User\UserGroup
145
     *
146
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to create a user group
147
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if the user group with the given id was not found
148
     */
149
    public function loadUserGroup($id)
150
    {
151
        $content = $this->repository->getContentService()->loadContent($id);
152
153
        return $this->buildDomainUserGroupObject($content);
154
    }
155
156
    /**
157
     * Loads the sub groups of a user group.
158
     *
159
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $userGroup
160
     * @param int $offset the start offset for paging
161
     * @param int $limit the number of user groups returned
162
     *
163
     * @return \eZ\Publish\API\Repository\Values\User\UserGroup[]
164
     *
165
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to read the user group
166
     */
167
    public function loadSubUserGroups(APIUserGroup $userGroup, $offset = 0, $limit = 25)
168
    {
169
        $locationService = $this->repository->getLocationService();
170
171
        $loadedUserGroup = $this->loadUserGroup($userGroup->id);
172
        if (!$this->repository->canUser('content', 'read', $loadedUserGroup)) {
173
            throw new UnauthorizedException('content', 'read');
174
        }
175
176
        if ($loadedUserGroup->getVersionInfo()->getContentInfo()->mainLocationId === null) {
177
            return array();
178
        }
179
180
        $mainGroupLocation = $locationService->loadLocation(
181
            $loadedUserGroup->getVersionInfo()->getContentInfo()->mainLocationId
182
        );
183
184
        $searchResult = $this->searchSubGroups(
185
            $mainGroupLocation->id,
186
            $mainGroupLocation->sortField,
187
            $mainGroupLocation->sortOrder,
188
            $offset,
189
            $limit
190
        );
191
        if ($searchResult->totalCount == 0) {
192
            return array();
193
        }
194
195
        $subUserGroups = array();
196 View Code Duplication
        foreach ($searchResult->searchHits as $searchHit) {
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...
197
            $subUserGroups[] = $this->buildDomainUserGroupObject(
198
                $this->repository->getContentService()->internalLoadContent(
0 ignored issues
show
Bug introduced by
The method internalLoadContent() does not exist on eZ\Publish\API\Repository\ContentService. Did you maybe mean loadContent()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
199
                    $searchHit->valueObject->contentInfo->id
0 ignored issues
show
Documentation introduced by
The property contentInfo does not exist on object<eZ\Publish\API\Re...ory\Values\ValueObject>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
200
                )
201
            );
202
        }
203
204
        return $subUserGroups;
205
    }
206
207
    /**
208
     * Returns (searches) subgroups of a user group described by its main location.
209
     *
210
     * @param mixed $locationId
211
     * @param int|null $sortField
212
     * @param int $sortOrder
213
     * @param int $offset
214
     * @param int $limit
215
     *
216
     * @return \eZ\Publish\API\Repository\Values\Content\Search\SearchResult
217
     */
218
    protected function searchSubGroups($locationId, $sortField = null, $sortOrder = Location::SORT_ORDER_ASC, $offset = 0, $limit = 25)
219
    {
220
        $searchQuery = new LocationQuery();
221
222
        $searchQuery->offset = $offset;
223
        $searchQuery->limit = $limit;
224
225
        $searchQuery->filter = new CriterionLogicalAnd(
226
            array(
227
                new CriterionContentTypeId($this->settings['userGroupClassID']),
228
                new CriterionParentLocationId($locationId),
229
            )
230
        );
231
232
        $searchQuery->sortClauses = array();
233
        if ($sortField !== null) {
234
            $searchQuery->sortClauses[] = $this->getSortClauseBySortField($sortField, $sortOrder);
235
        }
236
237
        return $this->repository->getSearchService()->findLocations($searchQuery, array(), false);
238
    }
239
240
    /**
241
     * Removes a user group.
242
     *
243
     * the users which are not assigned to other groups will be deleted.
244
     *
245
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $userGroup
246
     *
247
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to create a user group
248
     */
249 View Code Duplication
    public function deleteUserGroup(APIUserGroup $userGroup)
250
    {
251
        $loadedUserGroup = $this->loadUserGroup($userGroup->id);
252
253
        $this->repository->beginTransaction();
254
        try {
255
            //@todo: what happens to sub user groups and users below sub user groups
256
            $this->repository->getContentService()->deleteContent($loadedUserGroup->getVersionInfo()->getContentInfo());
257
            $this->repository->commit();
258
        } catch (Exception $e) {
259
            $this->repository->rollback();
260
            throw $e;
261
        }
262
    }
263
264
    /**
265
     * Moves the user group to another parent.
266
     *
267
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $userGroup
268
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $newParent
269
     *
270
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to move the user group
271
     */
272
    public function moveUserGroup(APIUserGroup $userGroup, APIUserGroup $newParent)
273
    {
274
        $loadedUserGroup = $this->loadUserGroup($userGroup->id);
275
        $loadedNewParent = $this->loadUserGroup($newParent->id);
276
277
        $locationService = $this->repository->getLocationService();
278
279
        if ($loadedUserGroup->getVersionInfo()->getContentInfo()->mainLocationId === null) {
280
            throw new BadStateException('userGroup', 'existing user group is not stored and/or does not have any location yet');
281
        }
282
283
        if ($loadedNewParent->getVersionInfo()->getContentInfo()->mainLocationId === null) {
284
            throw new BadStateException('newParent', 'new user group is not stored and/or does not have any location yet');
285
        }
286
287
        $userGroupMainLocation = $locationService->loadLocation(
288
            $loadedUserGroup->getVersionInfo()->getContentInfo()->mainLocationId
289
        );
290
        $newParentMainLocation = $locationService->loadLocation(
291
            $loadedNewParent->getVersionInfo()->getContentInfo()->mainLocationId
292
        );
293
294
        $this->repository->beginTransaction();
295
        try {
296
            $locationService->moveSubtree($userGroupMainLocation, $newParentMainLocation);
297
            $this->repository->commit();
298
        } catch (Exception $e) {
299
            $this->repository->rollback();
300
            throw $e;
301
        }
302
    }
303
304
    /**
305
     * Updates the group profile with fields and meta data.
306
     *
307
     * 4.x: If the versionUpdateStruct is set in $userGroupUpdateStruct, this method internally creates a content draft, updates ts with the provided data
308
     * and publishes the draft. If a draft is explicitly required, the user group can be updated via the content service methods.
309
     *
310
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $userGroup
311
     * @param \eZ\Publish\API\Repository\Values\User\UserGroupUpdateStruct $userGroupUpdateStruct
312
     *
313
     * @return \eZ\Publish\API\Repository\Values\User\UserGroup
314
     *
315
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to update the user group
316
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $userGroupUpdateStruct is not valid
317
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException if a required field is set empty
318
     */
319
    public function updateUserGroup(APIUserGroup $userGroup, UserGroupUpdateStruct $userGroupUpdateStruct)
320
    {
321
        if ($userGroupUpdateStruct->contentUpdateStruct === null &&
322
            $userGroupUpdateStruct->contentMetadataUpdateStruct === null) {
323
            // both update structs are empty, nothing to do
324
            return $userGroup;
325
        }
326
327
        $contentService = $this->repository->getContentService();
328
329
        $loadedUserGroup = $this->loadUserGroup($userGroup->id);
330
331
        $this->repository->beginTransaction();
332
        try {
333
            $publishedContent = $loadedUserGroup;
334 View Code Duplication
            if ($userGroupUpdateStruct->contentUpdateStruct !== null) {
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...
335
                $contentDraft = $contentService->createContentDraft($loadedUserGroup->getVersionInfo()->getContentInfo());
336
337
                $contentDraft = $contentService->updateContent(
338
                    $contentDraft->getVersionInfo(),
339
                    $userGroupUpdateStruct->contentUpdateStruct
340
                );
341
342
                $publishedContent = $contentService->publishVersion($contentDraft->getVersionInfo());
343
            }
344
345
            if ($userGroupUpdateStruct->contentMetadataUpdateStruct !== null) {
346
                $publishedContent = $contentService->updateContentMetadata(
347
                    $publishedContent->getVersionInfo()->getContentInfo(),
348
                    $userGroupUpdateStruct->contentMetadataUpdateStruct
349
                );
350
            }
351
352
            $this->repository->commit();
353
        } catch (Exception $e) {
354
            $this->repository->rollback();
355
            throw $e;
356
        }
357
358
        return $this->buildDomainUserGroupObject($publishedContent);
359
    }
360
361
    /**
362
     * Create a new user. The created user is published by this method.
363
     *
364
     * @param \eZ\Publish\API\Repository\Values\User\UserCreateStruct $userCreateStruct the data used for creating the user
365
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup[] $parentGroups the groups which are assigned to the user after creation
366
     *
367
     * @return \eZ\Publish\API\Repository\Values\User\User
368
     *
369
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to move the user group
370
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $userCreateStruct is not valid
371
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException if a required field is missing or set to an empty value
372
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if a user with provided login already exists
373
     */
374
    public function createUser(APIUserCreateStruct $userCreateStruct, array $parentGroups)
375
    {
376
        if (empty($parentGroups)) {
377
            throw new InvalidArgumentValue('parentGroups', $parentGroups);
378
        }
379
380
        if (!is_string($userCreateStruct->login) || empty($userCreateStruct->login)) {
381
            throw new InvalidArgumentValue('login', $userCreateStruct->login, 'UserCreateStruct');
382
        }
383
384 View Code Duplication
        if (!is_string($userCreateStruct->email) || empty($userCreateStruct->email)) {
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...
385
            throw new InvalidArgumentValue('email', $userCreateStruct->email, 'UserCreateStruct');
386
        }
387
388
        if (!ezcMailTools::validateEmailAddress($userCreateStruct->email)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression \ezcMailTools::validateE...serCreateStruct->email) of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
389
            throw new InvalidArgumentValue('email', $userCreateStruct->email, 'UserCreateStruct');
390
        }
391
392
        if (!is_string($userCreateStruct->password) || empty($userCreateStruct->password)) {
393
            throw new InvalidArgumentValue('password', $userCreateStruct->password, 'UserCreateStruct');
394
        }
395
396
        if (!is_bool($userCreateStruct->enabled)) {
397
            throw new InvalidArgumentValue('enabled', $userCreateStruct->enabled, 'UserCreateStruct');
398
        }
399
400
        try {
401
            $this->userHandler->loadByLogin($userCreateStruct->login);
402
            throw new InvalidArgumentException('userCreateStruct', 'User with provided login already exists');
403
        } catch (NotFoundException $e) {
404
            // Do nothing
405
        }
406
407
        $contentService = $this->repository->getContentService();
408
        $locationService = $this->repository->getLocationService();
409
        $contentTypeService = $this->repository->getContentTypeService();
410
411
        if ($userCreateStruct->contentType === null) {
412
            $userContentType = $contentTypeService->loadContentType($this->settings['userClassID']);
413
            $userCreateStruct->contentType = $userContentType;
414
        }
415
416
        $locationCreateStructs = array();
417
        foreach ($parentGroups as $parentGroup) {
418
            $parentGroup = $this->loadUserGroup($parentGroup->id);
419
            if ($parentGroup->getVersionInfo()->getContentInfo()->mainLocationId !== null) {
420
                $locationCreateStructs[] = $locationService->newLocationCreateStruct(
421
                    $parentGroup->getVersionInfo()->getContentInfo()->mainLocationId
422
                );
423
            }
424
        }
425
426
        // Search for the first ezuser field type in content type
427
        $userFieldDefinition = null;
428
        foreach ($userCreateStruct->contentType->getFieldDefinitions() as $fieldDefinition) {
429
            if ($fieldDefinition->fieldTypeIdentifier == 'ezuser') {
430
                $userFieldDefinition = $fieldDefinition;
431
                break;
432
            }
433
        }
434
435
        if ($userFieldDefinition === null) {
436
            throw new ContentValidationException('Provided content type does not contain ezuser field type');
437
        }
438
439
        $fixUserFieldType = true;
440
        foreach ($userCreateStruct->fields as $index => $field) {
441
            if ($field->fieldDefIdentifier == $userFieldDefinition->identifier) {
442
                if ($field->value instanceof UserValue) {
0 ignored issues
show
Documentation introduced by
The property $value is declared protected in eZ\Publish\API\Repository\Values\Content\Field. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
443
                    $userCreateStruct->fields[$index]->value->login = $userCreateStruct->login;
0 ignored issues
show
Documentation introduced by
The property $value is declared protected in eZ\Publish\API\Repository\Values\Content\Field. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
444
                } else {
445
                    $userCreateStruct->fields[$index]->value = new UserValue(
0 ignored issues
show
Documentation introduced by
The property $value is declared protected in eZ\Publish\API\Repository\Values\Content\Field. Since you implemented __set(), maybe consider adding a @property or @property-write annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
446
                        array(
447
                            'login' => $userCreateStruct->login,
448
                        )
449
                    );
450
                }
451
452
                $fixUserFieldType = false;
453
            }
454
        }
455
456
        if ($fixUserFieldType) {
457
            $userCreateStruct->setField(
458
                $userFieldDefinition->identifier,
459
                new UserValue(
460
                    array(
461
                        'login' => $userCreateStruct->login,
462
                    )
463
                )
464
            );
465
        }
466
467
        $this->repository->beginTransaction();
468
        try {
469
            $contentDraft = $contentService->createContent($userCreateStruct, $locationCreateStructs);
470
            // Create user before publishing, so that external data can be returned
471
            $spiUser = $this->userHandler->create(
472
                new SPIUser(
473
                    array(
474
                        'id' => $contentDraft->id,
475
                        'login' => $userCreateStruct->login,
476
                        'email' => $userCreateStruct->email,
477
                        'passwordHash' => $this->createPasswordHash(
478
                            $userCreateStruct->login,
479
                            $userCreateStruct->password,
480
                            $this->settings['siteName'],
481
                            $this->settings['hashType']
482
                        ),
483
                        'hashAlgorithm' => $this->settings['hashType'],
484
                        'isEnabled' => $userCreateStruct->enabled,
485
                        'maxLogin' => 0,
486
                    )
487
                )
488
            );
489
            $publishedContent = $contentService->publishVersion($contentDraft->getVersionInfo());
490
491
            $this->repository->commit();
492
        } catch (Exception $e) {
493
            $this->repository->rollback();
494
            throw $e;
495
        }
496
497
        return $this->buildDomainUserObject($spiUser, $publishedContent);
498
    }
499
500
    /**
501
     * Loads a user.
502
     *
503
     * @param mixed $userId
504
     *
505
     * @return \eZ\Publish\API\Repository\Values\User\User
506
     *
507
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if a user with the given id was not found
508
     */
509
    public function loadUser($userId)
510
    {
511
        /** @var \eZ\Publish\API\Repository\Values\Content\Content $content */
512
        $content = $this->repository->getContentService()->internalLoadContent($userId);
0 ignored issues
show
Bug introduced by
The method internalLoadContent() does not exist on eZ\Publish\API\Repository\ContentService. Did you maybe mean loadContent()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
513
        // Get spiUser value from Field Value
514
        foreach ($content->getFields() as $field) {
515
            if (!$field->value instanceof UserValue) {
0 ignored issues
show
Documentation introduced by
The property $value is declared protected in eZ\Publish\API\Repository\Values\Content\Field. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
516
                continue;
517
            }
518
519
            /** @var \eZ\Publish\Core\FieldType\User\Value $value */
520
            $value = $field->value;
0 ignored issues
show
Documentation introduced by
The property $value is declared protected in eZ\Publish\API\Repository\Values\Content\Field. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
521
            $spiUser = new SPIUser();
522
            $spiUser->id = $value->contentId;
523
            $spiUser->login = $value->login;
524
            $spiUser->email = $value->email;
525
            $spiUser->hashAlgorithm = $value->passwordHashType;
526
            $spiUser->passwordHash = $value->passwordHash;
527
            $spiUser->isEnabled = $value->enabled;
528
            $spiUser->maxLogin = $value->maxLogin;
529
            break;
530
        }
531
532
        // If for some reason not found, load it
533
        if (!isset($spiUser)) {
534
            $spiUser = $this->userHandler->load($userId);
535
        }
536
537
        return $this->buildDomainUserObject($spiUser, $content);
538
    }
539
540
    /**
541
     * Loads anonymous user.
542
     *
543
     * @deprecated since 5.3, use loadUser( $anonymousUserId ) instead
544
     *
545
     * @uses loadUser()
546
     *
547
     * @return \eZ\Publish\API\Repository\Values\User\User
548
     */
549
    public function loadAnonymousUser()
550
    {
551
        return $this->loadUser($this->settings['anonymousUserID']);
552
    }
553
554
    /**
555
     * Loads a user for the given login and password.
556
     *
557
     * {@inheritdoc}
558
     *
559
     * @param string $login
560
     * @param string $password the plain password
561
     *
562
     * @return \eZ\Publish\API\Repository\Values\User\User
563
     *
564
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if credentials are invalid
565
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if a user with the given credentials was not found
566
     */
567
    public function loadUserByCredentials($login, $password)
568
    {
569
        if (!is_string($login) || empty($login)) {
570
            throw new InvalidArgumentValue('login', $login);
571
        }
572
573
        if (!is_string($password)) {
574
            throw new InvalidArgumentValue('password', $password);
575
        }
576
577
        // Randomize login time to protect against timing attacks
578
        usleep(mt_rand(0, 30000));
579
580
        $spiUser = $this->userHandler->loadByLogin($login);
581
        $passwordHash = $this->createPasswordHash(
582
            $login,
583
            $password,
584
            $this->settings['siteName'],
585
            $spiUser->hashAlgorithm
586
        );
587
588
        if ($spiUser->passwordHash !== $passwordHash) {
589
            throw new NotFoundException('user', $login);
590
        }
591
592
        return $this->buildDomainUserObject($spiUser);
593
    }
594
595
    /**
596
     * Loads a user for the given login.
597
     *
598
     * {@inheritdoc}
599
     *
600
     * @param string $login
601
     *
602
     * @return \eZ\Publish\API\Repository\Values\User\User
603
     *
604
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if a user with the given credentials was not found
605
     */
606
    public function loadUserByLogin($login)
607
    {
608
        if (!is_string($login) || empty($login)) {
609
            throw new InvalidArgumentValue('login', $login);
610
        }
611
612
        $spiUser = $this->userHandler->loadByLogin($login);
613
614
        return $this->buildDomainUserObject($spiUser);
615
    }
616
617
    /**
618
     * Loads a user for the given email.
619
     *
620
     * {@inheritdoc}
621
     *
622
     * @param string $email
623
     *
624
     * @return \eZ\Publish\API\Repository\Values\User\User[]
625
     */
626
    public function loadUsersByEmail($email)
627
    {
628
        if (!is_string($email) || empty($email)) {
629
            throw new InvalidArgumentValue('email', $email);
630
        }
631
632
        $users = array();
633
        foreach ($this->userHandler->loadByEmail($email) as $spiUser) {
634
            $users[] = $this->buildDomainUserObject($spiUser);
635
        }
636
637
        return $users;
638
    }
639
640
    /**
641
     * This method deletes a user.
642
     *
643
     * @param \eZ\Publish\API\Repository\Values\User\User $user
644
     *
645
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to delete the user
646
     */
647 View Code Duplication
    public function deleteUser(APIUser $user)
648
    {
649
        $loadedUser = $this->loadUser($user->id);
650
651
        $this->repository->beginTransaction();
652
        try {
653
            $this->repository->getContentService()->deleteContent($loadedUser->getVersionInfo()->getContentInfo());
654
            $this->userHandler->delete($loadedUser->id);
655
            $this->repository->commit();
656
        } catch (Exception $e) {
657
            $this->repository->rollback();
658
            throw $e;
659
        }
660
    }
661
662
    /**
663
     * Updates a user.
664
     *
665
     * 4.x: If the versionUpdateStruct is set in the user update structure, this method internally creates a content draft, updates ts with the provided data
666
     * and publishes the draft. If a draft is explicitly required, the user group can be updated via the content service methods.
667
     *
668
     * @param \eZ\Publish\API\Repository\Values\User\User $user
669
     * @param \eZ\Publish\API\Repository\Values\User\UserUpdateStruct $userUpdateStruct
670
     *
671
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to update the user
672
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $userUpdateStruct is not valid
673
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException if a required field is set empty
674
     *
675
     * @return \eZ\Publish\API\Repository\Values\User\User
676
     */
677
    public function updateUser(APIUser $user, UserUpdateStruct $userUpdateStruct)
678
    {
679
        $loadedUser = $this->loadUser($user->id);
680
681
        // We need to determine if we have anything to update.
682
        // UserUpdateStruct is specific as some of the new content is in
683
        // content update struct and some of it is in additional fields like
684
        // email, password and so on
685
        $doUpdate = false;
686
        foreach ($userUpdateStruct as $propertyValue) {
0 ignored issues
show
Bug introduced by
The expression $userUpdateStruct of type object<eZ\Publish\API\Re...\User\UserUpdateStruct> is not traversable.
Loading history...
687
            if ($propertyValue !== null) {
688
                $doUpdate = true;
689
                break;
690
            }
691
        }
692
693
        if (!$doUpdate) {
694
            // Nothing to update, so we just quit
695
            return $user;
696
        }
697
698
        if ($userUpdateStruct->email !== null) {
699 View Code Duplication
            if (!is_string($userUpdateStruct->email) || empty($userUpdateStruct->email)) {
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...
700
                throw new InvalidArgumentValue('email', $userUpdateStruct->email, 'UserUpdateStruct');
701
            }
702
703
            if (!ezcMailTools::validateEmailAddress($userUpdateStruct->email)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression \ezcMailTools::validateE...serUpdateStruct->email) of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
704
                throw new InvalidArgumentValue('email', $userUpdateStruct->email, 'UserUpdateStruct');
705
            }
706
        }
707
708 View Code Duplication
        if ($userUpdateStruct->password !== null && (!is_string($userUpdateStruct->password) || empty($userUpdateStruct->password))) {
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...
709
            throw new InvalidArgumentValue('password', $userUpdateStruct->password, 'UserUpdateStruct');
710
        }
711
712
        if ($userUpdateStruct->enabled !== null && !is_bool($userUpdateStruct->enabled)) {
713
            throw new InvalidArgumentValue('enabled', $userUpdateStruct->enabled, 'UserUpdateStruct');
714
        }
715
716
        if ($userUpdateStruct->maxLogin !== null && !is_int($userUpdateStruct->maxLogin)) {
717
            throw new InvalidArgumentValue('maxLogin', $userUpdateStruct->maxLogin, 'UserUpdateStruct');
718
        }
719
720
        $contentService = $this->repository->getContentService();
721
722
        if (!$this->repository->canUser('content', 'edit', $loadedUser)) {
723
            throw new UnauthorizedException('content', 'edit');
724
        }
725
726
        $this->repository->beginTransaction();
727
        try {
728
            $publishedContent = $loadedUser;
729 View Code Duplication
            if ($userUpdateStruct->contentUpdateStruct !== null) {
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...
730
                $contentDraft = $contentService->createContentDraft($loadedUser->getVersionInfo()->getContentInfo());
731
                $contentDraft = $contentService->updateContent(
732
                    $contentDraft->getVersionInfo(),
733
                    $userUpdateStruct->contentUpdateStruct
734
                );
735
                $publishedContent = $contentService->publishVersion($contentDraft->getVersionInfo());
736
            }
737
738
            if ($userUpdateStruct->contentMetadataUpdateStruct !== null) {
739
                $contentService->updateContentMetadata(
740
                    $publishedContent->getVersionInfo()->getContentInfo(),
741
                    $userUpdateStruct->contentMetadataUpdateStruct
742
                );
743
            }
744
745
            $this->userHandler->update(
746
                new SPIUser(
747
                    array(
748
                        'id' => $loadedUser->id,
749
                        'login' => $loadedUser->login,
0 ignored issues
show
Documentation introduced by
The property $login is declared protected in eZ\Publish\API\Repository\Values\User\User. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
750
                        'email' => $userUpdateStruct->email ?: $loadedUser->email,
0 ignored issues
show
Documentation introduced by
The property $email is declared protected in eZ\Publish\API\Repository\Values\User\User. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
751
                        'passwordHash' => $userUpdateStruct->password ?
752
                            $this->createPasswordHash(
753
                                $loadedUser->login,
0 ignored issues
show
Documentation introduced by
The property $login is declared protected in eZ\Publish\API\Repository\Values\User\User. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
754
                                $userUpdateStruct->password,
755
                                $this->settings['siteName'],
756
                                $this->settings['hashType']
757
                            ) :
758
                            $loadedUser->passwordHash,
0 ignored issues
show
Documentation introduced by
The property $passwordHash is declared protected in eZ\Publish\API\Repository\Values\User\User. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
759
                        'hashAlgorithm' => $this->settings['hashType'],
760
                        'isEnabled' => $userUpdateStruct->enabled !== null ? $userUpdateStruct->enabled : $loadedUser->enabled,
0 ignored issues
show
Documentation introduced by
The property $enabled is declared protected in eZ\Publish\API\Repository\Values\User\User. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
761
                        'maxLogin' => $userUpdateStruct->maxLogin !== null ? (int)$userUpdateStruct->maxLogin : $loadedUser->maxLogin,
0 ignored issues
show
Documentation introduced by
The property $maxLogin is declared protected in eZ\Publish\API\Repository\Values\User\User. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
762
                    )
763
                )
764
            );
765
766
            $this->repository->commit();
767
        } catch (Exception $e) {
768
            $this->repository->rollback();
769
            throw $e;
770
        }
771
772
        return $this->loadUser($loadedUser->id);
773
    }
774
775
    /**
776
     * Assigns a new user group to the user.
777
     *
778
     * @param \eZ\Publish\API\Repository\Values\User\User $user
779
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $userGroup
780
     *
781
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to assign the user group to the user
782
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the user is already in the given user group
783
     */
784
    public function assignUserToUserGroup(APIUser $user, APIUserGroup $userGroup)
785
    {
786
        $loadedUser = $this->loadUser($user->id);
787
        $loadedGroup = $this->loadUserGroup($userGroup->id);
788
        $locationService = $this->repository->getLocationService();
789
790
        $existingGroupIds = array();
791
        $userLocations = $locationService->loadLocations($loadedUser->getVersionInfo()->getContentInfo());
792
        foreach ($userLocations as $userLocation) {
793
            $existingGroupIds[] = $userLocation->parentLocationId;
794
        }
795
796
        if ($loadedGroup->getVersionInfo()->getContentInfo()->mainLocationId === null) {
797
            throw new BadStateException('userGroup', 'user group has no main location or no locations');
798
        }
799
800
        if (in_array($loadedGroup->getVersionInfo()->getContentInfo()->mainLocationId, $existingGroupIds)) {
801
            // user is already assigned to the user group
802
            throw new InvalidArgumentException('user', 'user is already in the given user group');
803
        }
804
805
        $locationCreateStruct = $locationService->newLocationCreateStruct(
806
            $loadedGroup->getVersionInfo()->getContentInfo()->mainLocationId
807
        );
808
809
        $this->repository->beginTransaction();
810
        try {
811
            $locationService->createLocation(
812
                $loadedUser->getVersionInfo()->getContentInfo(),
813
                $locationCreateStruct
814
            );
815
            $this->repository->commit();
816
        } catch (Exception $e) {
817
            $this->repository->rollback();
818
            throw $e;
819
        }
820
    }
821
822
    /**
823
     * Removes a user group from the user.
824
     *
825
     * @param \eZ\Publish\API\Repository\Values\User\User $user
826
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $userGroup
827
     *
828
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to remove the user group from the user
829
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the user is not in the given user group
830
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException If $userGroup is the last assigned user group
831
     */
832
    public function unAssignUserFromUserGroup(APIUser $user, APIUserGroup $userGroup)
833
    {
834
        $loadedUser = $this->loadUser($user->id);
835
        $loadedGroup = $this->loadUserGroup($userGroup->id);
836
        $locationService = $this->repository->getLocationService();
837
838
        $userLocations = $locationService->loadLocations($loadedUser->getVersionInfo()->getContentInfo());
839
        if (empty($userLocations)) {
840
            throw new BadStateException('user', 'user has no locations, cannot unassign from group');
841
        }
842
843
        if ($loadedGroup->getVersionInfo()->getContentInfo()->mainLocationId === null) {
844
            throw new BadStateException('userGroup', 'user group has no main location or no locations, cannot unassign');
845
        }
846
847
        foreach ($userLocations as $userLocation) {
848
            if ($userLocation->parentLocationId == $loadedGroup->getVersionInfo()->getContentInfo()->mainLocationId) {
849
                // Throw this specific BadState when we know argument is valid
850
                if (count($userLocations) === 1) {
851
                    throw new BadStateException('user', 'user only has one user group, cannot unassign from last group');
852
                }
853
854
                $this->repository->beginTransaction();
855
                try {
856
                    $locationService->deleteLocation($userLocation);
857
                    $this->repository->commit();
858
859
                    return;
860
                } catch (Exception $e) {
861
                    $this->repository->rollback();
862
                    throw $e;
863
                }
864
            }
865
        }
866
867
        throw new InvalidArgumentException('userGroup', 'user is not in the given user group');
868
    }
869
870
    /**
871
     * Loads the user groups the user belongs to.
872
     *
873
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed read the user or user group
874
     *
875
     * @param \eZ\Publish\API\Repository\Values\User\User $user
876
     * @param int $offset the start offset for paging
877
     * @param int $limit the number of user groups returned
878
     *
879
     * @return \eZ\Publish\API\Repository\Values\User\UserGroup[]
880
     */
881
    public function loadUserGroupsOfUser(APIUser $user, $offset = 0, $limit = 25)
882
    {
883
        $locationService = $this->repository->getLocationService();
884
885
        if (!$this->repository->canUser('content', 'read', $user)) {
886
            throw new UnauthorizedException('content', 'read');
887
        }
888
889
        $userLocations = $locationService->loadLocations(
890
            $user->getVersionInfo()->getContentInfo()
891
        );
892
893
        $parentLocationIds = array();
894
        foreach ($userLocations as $userLocation) {
895
            if ($userLocation->parentLocationId !== null) {
896
                $parentLocationIds[] = $userLocation->parentLocationId;
897
            }
898
        }
899
900
        $searchQuery = new LocationQuery();
901
902
        $searchQuery->offset = $offset;
903
        $searchQuery->limit = $limit;
904
        $searchQuery->performCount = false;
905
906
        $searchQuery->filter = new CriterionLogicalAnd(
907
            array(
908
                new CriterionContentTypeId($this->settings['userGroupClassID']),
909
                new CriterionLocationId($parentLocationIds),
910
            )
911
        );
912
913
        $searchResult = $this->repository->getSearchService()->findLocations($searchQuery);
914
915
        $userGroups = array();
916 View Code Duplication
        foreach ($searchResult->searchHits as $resultItem) {
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...
917
            $userGroups[] = $this->buildDomainUserGroupObject(
918
                $this->repository->getContentService()->internalLoadContent(
0 ignored issues
show
Bug introduced by
The method internalLoadContent() does not exist on eZ\Publish\API\Repository\ContentService. Did you maybe mean loadContent()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
919
                    $resultItem->valueObject->contentInfo->id
0 ignored issues
show
Documentation introduced by
The property contentInfo does not exist on object<eZ\Publish\API\Re...ory\Values\ValueObject>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
920
                )
921
            );
922
        }
923
924
        return $userGroups;
925
    }
926
927
    /**
928
     * Loads the users of a user group.
929
     *
930
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to read the users or user group
931
     *
932
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $userGroup
933
     * @param int $offset the start offset for paging
934
     * @param int $limit the number of users returned
935
     *
936
     * @return \eZ\Publish\API\Repository\Values\User\User[]
937
     */
938
    public function loadUsersOfUserGroup(APIUserGroup $userGroup, $offset = 0, $limit = 25)
939
    {
940
        $loadedUserGroup = $this->loadUserGroup($userGroup->id);
941
942
        if ($loadedUserGroup->getVersionInfo()->getContentInfo()->mainLocationId === null) {
943
            return array();
944
        }
945
946
        $mainGroupLocation = $this->repository->getLocationService()->loadLocation(
947
            $loadedUserGroup->getVersionInfo()->getContentInfo()->mainLocationId
948
        );
949
950
        $searchQuery = new LocationQuery();
951
952
        $searchQuery->filter = new CriterionLogicalAnd(
953
            array(
954
                new CriterionContentTypeId($this->settings['userClassID']),
955
                new CriterionParentLocationId($mainGroupLocation->id),
956
            )
957
        );
958
959
        $searchQuery->offset = $offset;
960
        $searchQuery->limit = $limit;
961
        $searchQuery->performCount = false;
962
963
        $searchQuery->sortClauses = array(
964
            $this->getSortClauseBySortField($mainGroupLocation->sortField, $mainGroupLocation->sortOrder),
965
        );
966
967
        $searchResult = $this->repository->getSearchService()->findLocations($searchQuery);
968
969
        $users = array();
970
        foreach ($searchResult->searchHits as $resultItem) {
971
            $users[] = $this->buildDomainUserObject(
972
                $this->userHandler->load($resultItem->valueObject->contentInfo->id),
0 ignored issues
show
Documentation introduced by
The property contentInfo does not exist on object<eZ\Publish\API\Re...ory\Values\ValueObject>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
973
                $this->repository->getContentService()->internalLoadContent(
0 ignored issues
show
Bug introduced by
The method internalLoadContent() does not exist on eZ\Publish\API\Repository\ContentService. Did you maybe mean loadContent()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
974
                    $resultItem->valueObject->contentInfo->id
0 ignored issues
show
Documentation introduced by
The property contentInfo does not exist on object<eZ\Publish\API\Re...ory\Values\ValueObject>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
975
                )
976
            );
977
        }
978
979
        return $users;
980
    }
981
982
    /**
983
     * Instantiate a user create class.
984
     *
985
     * @param string $login the login of the new user
986
     * @param string $email the email of the new user
987
     * @param string $password the plain password of the new user
988
     * @param string $mainLanguageCode the main language for the underlying content object
989
     * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType 5.x the content type for the underlying content object. In 4.x it is ignored and taken from the configuration
990
     *
991
     * @return \eZ\Publish\API\Repository\Values\User\UserCreateStruct
992
     */
993
    public function newUserCreateStruct($login, $email, $password, $mainLanguageCode, $contentType = null)
994
    {
995
        if ($contentType === null) {
996
            $contentType = $this->repository->getContentTypeService()->loadContentType(
997
                $this->settings['userClassID']
998
            );
999
        }
1000
1001
        return new UserCreateStruct(
1002
            array(
1003
                'contentType' => $contentType,
1004
                'mainLanguageCode' => $mainLanguageCode,
1005
                'login' => $login,
1006
                'email' => $email,
1007
                'password' => $password,
1008
                'enabled' => true,
1009
                'fields' => array(),
1010
            )
1011
        );
1012
    }
1013
1014
    /**
1015
     * Instantiate a user group create class.
1016
     *
1017
     * @param string $mainLanguageCode The main language for the underlying content object
1018
     * @param null|\eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType 5.x the content type for the underlying content object. In 4.x it is ignored and taken from the configuration
1019
     *
1020
     * @return \eZ\Publish\API\Repository\Values\User\UserGroupCreateStruct
1021
     */
1022
    public function newUserGroupCreateStruct($mainLanguageCode, $contentType = null)
1023
    {
1024
        if ($contentType === null) {
1025
            $contentType = $this->repository->getContentTypeService()->loadContentType(
1026
                $this->settings['userGroupClassID']
1027
            );
1028
        }
1029
1030
        return new UserGroupCreateStruct(
1031
            array(
1032
                'contentType' => $contentType,
1033
                'mainLanguageCode' => $mainLanguageCode,
1034
                'fields' => array(),
1035
            )
1036
        );
1037
    }
1038
1039
    /**
1040
     * Instantiate a new user update struct.
1041
     *
1042
     * @return \eZ\Publish\API\Repository\Values\User\UserUpdateStruct
1043
     */
1044
    public function newUserUpdateStruct()
1045
    {
1046
        return new UserUpdateStruct();
1047
    }
1048
1049
    /**
1050
     * Instantiate a new user group update struct.
1051
     *
1052
     * @return \eZ\Publish\API\Repository\Values\User\UserGroupUpdateStruct
1053
     */
1054
    public function newUserGroupUpdateStruct()
1055
    {
1056
        return new UserGroupUpdateStruct();
1057
    }
1058
1059
    /**
1060
     * Builds the domain UserGroup object from provided Content object.
1061
     *
1062
     * @param \eZ\Publish\API\Repository\Values\Content\Content $content
1063
     *
1064
     * @return \eZ\Publish\API\Repository\Values\User\UserGroup
1065
     */
1066
    protected function buildDomainUserGroupObject(APIContent $content)
1067
    {
1068
        $locationService = $this->repository->getLocationService();
1069
1070
        $subGroupCount = 0;
1071
        if ($content->getVersionInfo()->getContentInfo()->mainLocationId !== null) {
1072
            $mainLocation = $locationService->loadLocation(
1073
                $content->getVersionInfo()->getContentInfo()->mainLocationId
1074
            );
1075
            $parentLocation = $locationService->loadLocation($mainLocation->parentLocationId);
1076
            $subGroups = $this->searchSubGroups($mainLocation->id, null, Location::SORT_ORDER_ASC, 0, 0);
1077
            $subGroupCount = $subGroups->totalCount;
1078
        }
1079
1080
        return new UserGroup(
1081
            array(
1082
                'content' => $content,
1083
                'parentId' => isset($parentLocation) ? $parentLocation->contentId : null,
1084
                'subGroupCount' => $subGroupCount,
1085
            )
1086
        );
1087
    }
1088
1089
    /**
1090
     * Builds the domain user object from provided persistence user object.
1091
     *
1092
     * @param \eZ\Publish\SPI\Persistence\User $spiUser
1093
     * @param \eZ\Publish\API\Repository\Values\Content\Content|null $content
1094
     *
1095
     * @return \eZ\Publish\API\Repository\Values\User\User
1096
     */
1097
    protected function buildDomainUserObject(SPIUser $spiUser, APIContent $content = null)
1098
    {
1099
        if ($content === null) {
1100
            $content = $this->repository->getContentService()->internalLoadContent($spiUser->id);
0 ignored issues
show
Bug introduced by
The method internalLoadContent() does not exist on eZ\Publish\API\Repository\ContentService. Did you maybe mean loadContent()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
1101
        }
1102
1103
        return new User(
1104
            array(
1105
                'content' => $content,
1106
                'login' => $spiUser->login,
1107
                'email' => $spiUser->email,
1108
                'passwordHash' => $spiUser->passwordHash,
1109
                'hashAlgorithm' => (int)$spiUser->hashAlgorithm,
1110
                'enabled' => $spiUser->isEnabled,
1111
                'maxLogin' => (int)$spiUser->maxLogin,
1112
            )
1113
        );
1114
    }
1115
1116
    /**
1117
     * Returns password hash based on user data and site settings.
1118
     *
1119
     * @param string $login User login
1120
     * @param string $password User password
1121
     * @param string $site The name of the site
1122
     * @param int $type Type of password to generate
1123
     *
1124
     * @return string Generated password hash
1125
     */
1126
    protected function createPasswordHash($login, $password, $site, $type)
1127
    {
1128
        switch ($type) {
1129
            case User::PASSWORD_HASH_MD5_PASSWORD:
1130
                return md5($password);
1131
1132
            case User::PASSWORD_HASH_MD5_USER:
1133
                return md5("$login\n$password");
1134
1135
            case User::PASSWORD_HASH_MD5_SITE:
1136
                return md5("$login\n$password\n$site");
1137
1138
            case User::PASSWORD_HASH_PLAINTEXT:
1139
                return $password;
1140
1141
            default:
1142
                return md5($password);
1143
        }
1144
    }
1145
1146
    /**
1147
     * Instantiates a correct sort clause object based on provided location sort field and sort order.
1148
     *
1149
     * @param int $sortField
1150
     * @param int $sortOrder
1151
     *
1152
     * @return \eZ\Publish\API\Repository\Values\Content\Query\SortClause
1153
     */
1154
    protected function getSortClauseBySortField($sortField, $sortOrder = Location::SORT_ORDER_ASC)
1155
    {
1156
        $sortOrder = $sortOrder == Location::SORT_ORDER_DESC ? LocationQuery::SORT_DESC : LocationQuery::SORT_ASC;
1157
        switch ($sortField) {
1158
            case Location::SORT_FIELD_PATH:
1159
                return new SortClause\Location\Path($sortOrder);
1160
1161
            case Location::SORT_FIELD_PUBLISHED:
1162
                return new SortClause\DatePublished($sortOrder);
1163
1164
            case Location::SORT_FIELD_MODIFIED:
1165
                return new SortClause\DateModified($sortOrder);
1166
1167
            case Location::SORT_FIELD_SECTION:
1168
                return new SortClause\SectionIdentifier($sortOrder);
1169
1170
            case Location::SORT_FIELD_DEPTH:
1171
                return new SortClause\Location\Depth($sortOrder);
1172
1173
            //@todo: enable
1174
            // case APILocation::SORT_FIELD_CLASS_IDENTIFIER:
1175
1176
            //@todo: enable
1177
            // case APILocation::SORT_FIELD_CLASS_NAME:
1178
1179
            case Location::SORT_FIELD_PRIORITY:
1180
                return new SortClause\Location\Priority($sortOrder);
1181
1182
            case Location::SORT_FIELD_NAME:
1183
                return new SortClause\ContentName($sortOrder);
1184
1185
            //@todo: enable
1186
            // case APILocation::SORT_FIELD_MODIFIED_SUBNODE:
1187
1188
            //@todo: enable
1189
            // case APILocation::SORT_FIELD_NODE_ID:
1190
1191
            //@todo: enable
1192
            // case APILocation::SORT_FIELD_CONTENTOBJECT_ID:
1193
1194
            default:
1195
                return new SortClause\Location\Path($sortOrder);
1196
        }
1197
    }
1198
}
1199