Completed
Push — 6.1 ( 9744d5...08b231 )
by André
49:53 queued 22:16
created

UserService::createUser()   F

Complexity

Conditions 22
Paths 1482

Size

Total Lines 125
Code Lines 75

Duplication

Lines 3
Ratio 2.4 %

Importance

Changes 0
Metric Value
cc 22
eloc 75
nc 1482
nop 2
dl 3
loc 125
rs 2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/**
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
     * @param string $login
558
     * @param string $password the plain password
559
     *
560
     * @return \eZ\Publish\API\Repository\Values\User\User
561
     *
562
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if credentials are invalid
563
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if a user with the given credentials was not found
564
     */
565
    public function loadUserByCredentials($login, $password)
566
    {
567
        if (!is_string($login) || empty($login)) {
568
            throw new InvalidArgumentValue('login', $login);
569
        }
570
571
        if (!is_string($password)) {
572
            throw new InvalidArgumentValue('password', $password);
573
        }
574
575
        // Randomize login time to protect against timing attacks
576
        usleep(mt_rand(0, 30000));
577
578
        $spiUser = $this->userHandler->loadByLogin($login);
579
        $passwordHash = $this->createPasswordHash(
580
            $login,
581
            $password,
582
            $this->settings['siteName'],
583
            $spiUser->hashAlgorithm
584
        );
585
586
        if ($spiUser->passwordHash !== $passwordHash) {
587
            throw new NotFoundException('user', $login);
588
        }
589
590
        return $this->buildDomainUserObject($spiUser);
591
    }
592
593
    /**
594
     * Loads a user for the given login.
595
     *
596
     * @param string $login
597
     *
598
     * @return \eZ\Publish\API\Repository\Values\User\User
599
     *
600
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if a user with the given credentials was not found
601
     */
602
    public function loadUserByLogin($login)
603
    {
604
        if (!is_string($login) || empty($login)) {
605
            throw new InvalidArgumentValue('login', $login);
606
        }
607
608
        $spiUser = $this->userHandler->loadByLogin($login);
609
610
        return $this->buildDomainUserObject($spiUser);
611
    }
612
613
    /**
614
     * Loads a user for the given email.
615
     *
616
     * Returns an array of Users since eZ Publish has under certain circumstances allowed
617
     * several users having same email in the past (by means of a configuration option).
618
     *
619
     * @param string $email
620
     *
621
     * @return \eZ\Publish\API\Repository\Values\User\User[]
622
     */
623
    public function loadUsersByEmail($email)
624
    {
625
        if (!is_string($email) || empty($email)) {
626
            throw new InvalidArgumentValue('email', $email);
627
        }
628
629
        $users = array();
630
        foreach ($this->userHandler->loadByEmail($email) as $spiUser) {
631
            $users[] = $this->buildDomainUserObject($spiUser);
632
        }
633
634
        return $users;
635
    }
636
637
    /**
638
     * This method deletes a user.
639
     *
640
     * @param \eZ\Publish\API\Repository\Values\User\User $user
641
     *
642
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to delete the user
643
     */
644 View Code Duplication
    public function deleteUser(APIUser $user)
645
    {
646
        $loadedUser = $this->loadUser($user->id);
647
648
        $this->repository->beginTransaction();
649
        try {
650
            $this->repository->getContentService()->deleteContent($loadedUser->getVersionInfo()->getContentInfo());
651
            $this->userHandler->delete($loadedUser->id);
652
            $this->repository->commit();
653
        } catch (Exception $e) {
654
            $this->repository->rollback();
655
            throw $e;
656
        }
657
    }
658
659
    /**
660
     * Updates a user.
661
     *
662
     * 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
663
     * and publishes the draft. If a draft is explicitly required, the user group can be updated via the content service methods.
664
     *
665
     * @param \eZ\Publish\API\Repository\Values\User\User $user
666
     * @param \eZ\Publish\API\Repository\Values\User\UserUpdateStruct $userUpdateStruct
667
     *
668
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to update the user
669
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $userUpdateStruct is not valid
670
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException if a required field is set empty
671
     *
672
     * @return \eZ\Publish\API\Repository\Values\User\User
673
     */
674
    public function updateUser(APIUser $user, UserUpdateStruct $userUpdateStruct)
675
    {
676
        $loadedUser = $this->loadUser($user->id);
677
678
        // We need to determine if we have anything to update.
679
        // UserUpdateStruct is specific as some of the new content is in
680
        // content update struct and some of it is in additional fields like
681
        // email, password and so on
682
        $doUpdate = false;
683
        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...
684
            if ($propertyValue !== null) {
685
                $doUpdate = true;
686
                break;
687
            }
688
        }
689
690
        if (!$doUpdate) {
691
            // Nothing to update, so we just quit
692
            return $user;
693
        }
694
695
        if ($userUpdateStruct->email !== null) {
696 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...
697
                throw new InvalidArgumentValue('email', $userUpdateStruct->email, 'UserUpdateStruct');
698
            }
699
700
            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...
701
                throw new InvalidArgumentValue('email', $userUpdateStruct->email, 'UserUpdateStruct');
702
            }
703
        }
704
705 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...
706
            throw new InvalidArgumentValue('password', $userUpdateStruct->password, 'UserUpdateStruct');
707
        }
708
709
        if ($userUpdateStruct->enabled !== null && !is_bool($userUpdateStruct->enabled)) {
710
            throw new InvalidArgumentValue('enabled', $userUpdateStruct->enabled, 'UserUpdateStruct');
711
        }
712
713
        if ($userUpdateStruct->maxLogin !== null && !is_int($userUpdateStruct->maxLogin)) {
714
            throw new InvalidArgumentValue('maxLogin', $userUpdateStruct->maxLogin, 'UserUpdateStruct');
715
        }
716
717
        $contentService = $this->repository->getContentService();
718
719
        if (!$this->repository->canUser('content', 'edit', $loadedUser)) {
720
            throw new UnauthorizedException('content', 'edit');
721
        }
722
723
        $this->repository->beginTransaction();
724
        try {
725
            $publishedContent = $loadedUser;
726 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...
727
                $contentDraft = $contentService->createContentDraft($loadedUser->getVersionInfo()->getContentInfo());
728
                $contentDraft = $contentService->updateContent(
729
                    $contentDraft->getVersionInfo(),
730
                    $userUpdateStruct->contentUpdateStruct
731
                );
732
                $publishedContent = $contentService->publishVersion($contentDraft->getVersionInfo());
733
            }
734
735
            if ($userUpdateStruct->contentMetadataUpdateStruct !== null) {
736
                $contentService->updateContentMetadata(
737
                    $publishedContent->getVersionInfo()->getContentInfo(),
738
                    $userUpdateStruct->contentMetadataUpdateStruct
739
                );
740
            }
741
742
            $this->userHandler->update(
743
                new SPIUser(
744
                    array(
745
                        'id' => $loadedUser->id,
746
                        '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...
747
                        '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...
748
                        'passwordHash' => $userUpdateStruct->password ?
749
                            $this->createPasswordHash(
750
                                $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...
751
                                $userUpdateStruct->password,
752
                                $this->settings['siteName'],
753
                                $this->settings['hashType']
754
                            ) :
755
                            $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...
756
                        'hashAlgorithm' => $this->settings['hashType'],
757
                        '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...
758
                        '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...
759
                    )
760
                )
761
            );
762
763
            $this->repository->commit();
764
        } catch (Exception $e) {
765
            $this->repository->rollback();
766
            throw $e;
767
        }
768
769
        return $this->loadUser($loadedUser->id);
770
    }
771
772
    /**
773
     * Assigns a new user group to the user.
774
     *
775
     * @param \eZ\Publish\API\Repository\Values\User\User $user
776
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $userGroup
777
     *
778
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to assign the user group to the user
779
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the user is already in the given user group
780
     */
781
    public function assignUserToUserGroup(APIUser $user, APIUserGroup $userGroup)
782
    {
783
        $loadedUser = $this->loadUser($user->id);
784
        $loadedGroup = $this->loadUserGroup($userGroup->id);
785
        $locationService = $this->repository->getLocationService();
786
787
        $existingGroupIds = array();
788
        $userLocations = $locationService->loadLocations($loadedUser->getVersionInfo()->getContentInfo());
789
        foreach ($userLocations as $userLocation) {
790
            $existingGroupIds[] = $userLocation->parentLocationId;
791
        }
792
793
        if ($loadedGroup->getVersionInfo()->getContentInfo()->mainLocationId === null) {
794
            throw new BadStateException('userGroup', 'user group has no main location or no locations');
795
        }
796
797
        if (in_array($loadedGroup->getVersionInfo()->getContentInfo()->mainLocationId, $existingGroupIds)) {
798
            // user is already assigned to the user group
799
            throw new InvalidArgumentException('user', 'user is already in the given user group');
800
        }
801
802
        $locationCreateStruct = $locationService->newLocationCreateStruct(
803
            $loadedGroup->getVersionInfo()->getContentInfo()->mainLocationId
804
        );
805
806
        $this->repository->beginTransaction();
807
        try {
808
            $locationService->createLocation(
809
                $loadedUser->getVersionInfo()->getContentInfo(),
810
                $locationCreateStruct
811
            );
812
            $this->repository->commit();
813
        } catch (Exception $e) {
814
            $this->repository->rollback();
815
            throw $e;
816
        }
817
    }
818
819
    /**
820
     * Removes a user group from the user.
821
     *
822
     * @param \eZ\Publish\API\Repository\Values\User\User $user
823
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $userGroup
824
     *
825
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to remove the user group from the user
826
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the user is not in the given user group
827
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException If $userGroup is the last assigned user group
828
     */
829
    public function unAssignUserFromUserGroup(APIUser $user, APIUserGroup $userGroup)
830
    {
831
        $loadedUser = $this->loadUser($user->id);
832
        $loadedGroup = $this->loadUserGroup($userGroup->id);
833
        $locationService = $this->repository->getLocationService();
834
835
        $userLocations = $locationService->loadLocations($loadedUser->getVersionInfo()->getContentInfo());
836
        if (empty($userLocations)) {
837
            throw new BadStateException('user', 'user has no locations, cannot unassign from group');
838
        }
839
840
        if ($loadedGroup->getVersionInfo()->getContentInfo()->mainLocationId === null) {
841
            throw new BadStateException('userGroup', 'user group has no main location or no locations, cannot unassign');
842
        }
843
844
        foreach ($userLocations as $userLocation) {
845
            if ($userLocation->parentLocationId == $loadedGroup->getVersionInfo()->getContentInfo()->mainLocationId) {
846
                // Throw this specific BadState when we know argument is valid
847
                if (count($userLocations) === 1) {
848
                    throw new BadStateException('user', 'user only has one user group, cannot unassign from last group');
849
                }
850
851
                $this->repository->beginTransaction();
852
                try {
853
                    $locationService->deleteLocation($userLocation);
854
                    $this->repository->commit();
855
856
                    return;
857
                } catch (Exception $e) {
858
                    $this->repository->rollback();
859
                    throw $e;
860
                }
861
            }
862
        }
863
864
        throw new InvalidArgumentException('userGroup', 'user is not in the given user group');
865
    }
866
867
    /**
868
     * Loads the user groups the user belongs to.
869
     *
870
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed read the user or user group
871
     *
872
     * @param \eZ\Publish\API\Repository\Values\User\User $user
873
     * @param int $offset the start offset for paging
874
     * @param int $limit the number of user groups returned
875
     *
876
     * @return \eZ\Publish\API\Repository\Values\User\UserGroup[]
877
     */
878
    public function loadUserGroupsOfUser(APIUser $user, $offset = 0, $limit = 25)
879
    {
880
        $locationService = $this->repository->getLocationService();
881
882
        if (!$this->repository->canUser('content', 'read', $user)) {
883
            throw new UnauthorizedException('content', 'read');
884
        }
885
886
        $userLocations = $locationService->loadLocations(
887
            $user->getVersionInfo()->getContentInfo()
888
        );
889
890
        $parentLocationIds = array();
891
        foreach ($userLocations as $userLocation) {
892
            if ($userLocation->parentLocationId !== null) {
893
                $parentLocationIds[] = $userLocation->parentLocationId;
894
            }
895
        }
896
897
        $searchQuery = new LocationQuery();
898
899
        $searchQuery->offset = $offset;
900
        $searchQuery->limit = $limit;
901
        $searchQuery->performCount = false;
902
903
        $searchQuery->filter = new CriterionLogicalAnd(
904
            array(
905
                new CriterionContentTypeId($this->settings['userGroupClassID']),
906
                new CriterionLocationId($parentLocationIds),
907
            )
908
        );
909
910
        $searchResult = $this->repository->getSearchService()->findLocations($searchQuery);
911
912
        $userGroups = array();
913 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...
914
            $userGroups[] = $this->buildDomainUserGroupObject(
915
                $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...
916
                    $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...
917
                )
918
            );
919
        }
920
921
        return $userGroups;
922
    }
923
924
    /**
925
     * Loads the users of a user group.
926
     *
927
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to read the users or user group
928
     *
929
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $userGroup
930
     * @param int $offset the start offset for paging
931
     * @param int $limit the number of users returned
932
     *
933
     * @return \eZ\Publish\API\Repository\Values\User\User[]
934
     */
935
    public function loadUsersOfUserGroup(APIUserGroup $userGroup, $offset = 0, $limit = 25)
936
    {
937
        $loadedUserGroup = $this->loadUserGroup($userGroup->id);
938
939
        if ($loadedUserGroup->getVersionInfo()->getContentInfo()->mainLocationId === null) {
940
            return array();
941
        }
942
943
        $mainGroupLocation = $this->repository->getLocationService()->loadLocation(
944
            $loadedUserGroup->getVersionInfo()->getContentInfo()->mainLocationId
945
        );
946
947
        $searchQuery = new LocationQuery();
948
949
        $searchQuery->filter = new CriterionLogicalAnd(
950
            array(
951
                new CriterionContentTypeId($this->settings['userClassID']),
952
                new CriterionParentLocationId($mainGroupLocation->id),
953
            )
954
        );
955
956
        $searchQuery->offset = $offset;
957
        $searchQuery->limit = $limit;
958
        $searchQuery->performCount = false;
959
960
        $searchQuery->sortClauses = array(
961
            $this->getSortClauseBySortField($mainGroupLocation->sortField, $mainGroupLocation->sortOrder),
962
        );
963
964
        $searchResult = $this->repository->getSearchService()->findLocations($searchQuery);
965
966
        $users = array();
967
        foreach ($searchResult->searchHits as $resultItem) {
968
            $users[] = $this->buildDomainUserObject(
969
                $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...
970
                $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...
971
                    $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...
972
                )
973
            );
974
        }
975
976
        return $users;
977
    }
978
979
    /**
980
     * Instantiate a user create class.
981
     *
982
     * @param string $login the login of the new user
983
     * @param string $email the email of the new user
984
     * @param string $password the plain password of the new user
985
     * @param string $mainLanguageCode the main language for the underlying content object
986
     * @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
987
     *
988
     * @return \eZ\Publish\API\Repository\Values\User\UserCreateStruct
989
     */
990
    public function newUserCreateStruct($login, $email, $password, $mainLanguageCode, $contentType = null)
991
    {
992
        if ($contentType === null) {
993
            $contentType = $this->repository->getContentTypeService()->loadContentType(
994
                $this->settings['userClassID']
995
            );
996
        }
997
998
        return new UserCreateStruct(
999
            array(
1000
                'contentType' => $contentType,
1001
                'mainLanguageCode' => $mainLanguageCode,
1002
                'login' => $login,
1003
                'email' => $email,
1004
                'password' => $password,
1005
                'enabled' => true,
1006
                'fields' => array(),
1007
            )
1008
        );
1009
    }
1010
1011
    /**
1012
     * Instantiate a user group create class.
1013
     *
1014
     * @param string $mainLanguageCode The main language for the underlying content object
1015
     * @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
1016
     *
1017
     * @return \eZ\Publish\API\Repository\Values\User\UserGroupCreateStruct
1018
     */
1019
    public function newUserGroupCreateStruct($mainLanguageCode, $contentType = null)
1020
    {
1021
        if ($contentType === null) {
1022
            $contentType = $this->repository->getContentTypeService()->loadContentType(
1023
                $this->settings['userGroupClassID']
1024
            );
1025
        }
1026
1027
        return new UserGroupCreateStruct(
1028
            array(
1029
                'contentType' => $contentType,
1030
                'mainLanguageCode' => $mainLanguageCode,
1031
                'fields' => array(),
1032
            )
1033
        );
1034
    }
1035
1036
    /**
1037
     * Instantiate a new user update struct.
1038
     *
1039
     * @return \eZ\Publish\API\Repository\Values\User\UserUpdateStruct
1040
     */
1041
    public function newUserUpdateStruct()
1042
    {
1043
        return new UserUpdateStruct();
1044
    }
1045
1046
    /**
1047
     * Instantiate a new user group update struct.
1048
     *
1049
     * @return \eZ\Publish\API\Repository\Values\User\UserGroupUpdateStruct
1050
     */
1051
    public function newUserGroupUpdateStruct()
1052
    {
1053
        return new UserGroupUpdateStruct();
1054
    }
1055
1056
    /**
1057
     * Builds the domain UserGroup object from provided Content object.
1058
     *
1059
     * @param \eZ\Publish\API\Repository\Values\Content\Content $content
1060
     *
1061
     * @return \eZ\Publish\API\Repository\Values\User\UserGroup
1062
     */
1063
    protected function buildDomainUserGroupObject(APIContent $content)
1064
    {
1065
        $locationService = $this->repository->getLocationService();
1066
1067
        $subGroupCount = 0;
1068
        if ($content->getVersionInfo()->getContentInfo()->mainLocationId !== null) {
1069
            $mainLocation = $locationService->loadLocation(
1070
                $content->getVersionInfo()->getContentInfo()->mainLocationId
1071
            );
1072
            $parentLocation = $locationService->loadLocation($mainLocation->parentLocationId);
1073
            $subGroups = $this->searchSubGroups($mainLocation->id, null, Location::SORT_ORDER_ASC, 0, 0);
1074
            $subGroupCount = $subGroups->totalCount;
1075
        }
1076
1077
        return new UserGroup(
1078
            array(
1079
                'content' => $content,
1080
                'parentId' => isset($parentLocation) ? $parentLocation->contentId : null,
1081
                'subGroupCount' => $subGroupCount,
1082
            )
1083
        );
1084
    }
1085
1086
    /**
1087
     * Builds the domain user object from provided persistence user object.
1088
     *
1089
     * @param \eZ\Publish\SPI\Persistence\User $spiUser
1090
     * @param \eZ\Publish\API\Repository\Values\Content\Content|null $content
1091
     *
1092
     * @return \eZ\Publish\API\Repository\Values\User\User
1093
     */
1094
    protected function buildDomainUserObject(SPIUser $spiUser, APIContent $content = null)
1095
    {
1096
        if ($content === null) {
1097
            $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...
1098
        }
1099
1100
        return new User(
1101
            array(
1102
                'content' => $content,
1103
                'login' => $spiUser->login,
1104
                'email' => $spiUser->email,
1105
                'passwordHash' => $spiUser->passwordHash,
1106
                'hashAlgorithm' => (int)$spiUser->hashAlgorithm,
1107
                'enabled' => $spiUser->isEnabled,
1108
                'maxLogin' => (int)$spiUser->maxLogin,
1109
            )
1110
        );
1111
    }
1112
1113
    /**
1114
     * Returns password hash based on user data and site settings.
1115
     *
1116
     * @param string $login User login
1117
     * @param string $password User password
1118
     * @param string $site The name of the site
1119
     * @param int $type Type of password to generate
1120
     *
1121
     * @return string Generated password hash
1122
     */
1123
    protected function createPasswordHash($login, $password, $site, $type)
1124
    {
1125
        switch ($type) {
1126
            case User::PASSWORD_HASH_MD5_PASSWORD:
1127
                return md5($password);
1128
1129
            case User::PASSWORD_HASH_MD5_USER:
1130
                return md5("$login\n$password");
1131
1132
            case User::PASSWORD_HASH_MD5_SITE:
1133
                return md5("$login\n$password\n$site");
1134
1135
            case User::PASSWORD_HASH_PLAINTEXT:
1136
                return $password;
1137
1138
            default:
1139
                return md5($password);
1140
        }
1141
    }
1142
1143
    /**
1144
     * Instantiates a correct sort clause object based on provided location sort field and sort order.
1145
     *
1146
     * @param int $sortField
1147
     * @param int $sortOrder
1148
     *
1149
     * @return \eZ\Publish\API\Repository\Values\Content\Query\SortClause
1150
     */
1151
    protected function getSortClauseBySortField($sortField, $sortOrder = Location::SORT_ORDER_ASC)
1152
    {
1153
        $sortOrder = $sortOrder == Location::SORT_ORDER_DESC ? LocationQuery::SORT_DESC : LocationQuery::SORT_ASC;
1154
        switch ($sortField) {
1155
            case Location::SORT_FIELD_PATH:
1156
                return new SortClause\Location\Path($sortOrder);
1157
1158
            case Location::SORT_FIELD_PUBLISHED:
1159
                return new SortClause\DatePublished($sortOrder);
1160
1161
            case Location::SORT_FIELD_MODIFIED:
1162
                return new SortClause\DateModified($sortOrder);
1163
1164
            case Location::SORT_FIELD_SECTION:
1165
                return new SortClause\SectionIdentifier($sortOrder);
1166
1167
            case Location::SORT_FIELD_DEPTH:
1168
                return new SortClause\Location\Depth($sortOrder);
1169
1170
            //@todo: enable
1171
            // case APILocation::SORT_FIELD_CLASS_IDENTIFIER:
1172
1173
            //@todo: enable
1174
            // case APILocation::SORT_FIELD_CLASS_NAME:
1175
1176
            case Location::SORT_FIELD_PRIORITY:
1177
                return new SortClause\Location\Priority($sortOrder);
1178
1179
            case Location::SORT_FIELD_NAME:
1180
                return new SortClause\ContentName($sortOrder);
1181
1182
            //@todo: enable
1183
            // case APILocation::SORT_FIELD_MODIFIED_SUBNODE:
1184
1185
            //@todo: enable
1186
            // case APILocation::SORT_FIELD_NODE_ID:
1187
1188
            //@todo: enable
1189
            // case APILocation::SORT_FIELD_CONTENTOBJECT_ID:
1190
1191
            default:
1192
                return new SortClause\Location\Path($sortOrder);
1193
        }
1194
    }
1195
}
1196