Completed
Push — 7.0 ( 3d7579...63ca94 )
by André
101:06 queued 75:39
created

UserService::createPasswordHash()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 25
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 16
nc 7
nop 4
dl 0
loc 25
rs 6.7272
c 0
b 0
f 0
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
namespace eZ\Publish\Core\Repository;
10
11
use eZ\Publish\API\Repository\Values\Content\LocationQuery;
12
use eZ\Publish\Core\Repository\Values\User\UserCreateStruct;
13
use eZ\Publish\API\Repository\Values\User\UserCreateStruct as APIUserCreateStruct;
14
use eZ\Publish\API\Repository\Values\User\UserUpdateStruct;
15
use eZ\Publish\Core\Repository\Values\User\User;
16
use eZ\Publish\API\Repository\Values\User\User as APIUser;
17
use eZ\Publish\Core\Repository\Values\User\UserGroup;
18
use eZ\Publish\API\Repository\Values\User\UserGroup as APIUserGroup;
19
use eZ\Publish\Core\Repository\Values\User\UserGroupCreateStruct;
20
use eZ\Publish\API\Repository\Values\User\UserGroupCreateStruct as APIUserGroupCreateStruct;
21
use eZ\Publish\API\Repository\Values\User\UserGroupUpdateStruct;
22
use eZ\Publish\API\Repository\Values\Content\Location;
23
use eZ\Publish\API\Repository\Values\Content\Content as APIContent;
24
use eZ\Publish\SPI\Persistence\User\Handler;
25
use eZ\Publish\API\Repository\Repository as RepositoryInterface;
26
use eZ\Publish\API\Repository\UserService as UserServiceInterface;
27
use eZ\Publish\SPI\Persistence\User as SPIUser;
28
use eZ\Publish\Core\FieldType\User\Value as UserValue;
29
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\LogicalAnd as CriterionLogicalAnd;
30
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\ContentTypeId as CriterionContentTypeId;
31
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\LocationId as CriterionLocationId;
32
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\ParentLocationId as CriterionParentLocationId;
33
use eZ\Publish\Core\Base\Exceptions\ContentValidationException;
34
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentValue;
35
use eZ\Publish\Core\Base\Exceptions\BadStateException;
36
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException;
37
use eZ\Publish\Core\Base\Exceptions\NotFoundException;
38
use eZ\Publish\Core\Base\Exceptions\UnauthorizedException;
39
use Exception;
40
41
/**
42
 * This service provides methods for managing users and user groups.
43
 *
44
 * @example Examples/user.php
45
 */
46
class UserService implements UserServiceInterface
47
{
48
    /**
49
     * @var \eZ\Publish\API\Repository\Repository
50
     */
51
    protected $repository;
52
53
    /**
54
     * @var \eZ\Publish\SPI\Persistence\User\Handler
55
     */
56
    protected $userHandler;
57
58
    /**
59
     * @var array
60
     */
61
    protected $settings;
62
63
    /**
64
     * Setups service with reference to repository object that created it & corresponding handler.
65
     *
66
     * @param \eZ\Publish\API\Repository\Repository $repository
67
     * @param \eZ\Publish\SPI\Persistence\User\Handler $userHandler
68
     * @param array $settings
69
     */
70
    public function __construct(RepositoryInterface $repository, Handler $userHandler, array $settings = array())
71
    {
72
        $this->repository = $repository;
73
        $this->userHandler = $userHandler;
74
        // Union makes sure default settings are ignored if provided in argument
75
        $this->settings = $settings + array(
76
            'defaultUserPlacement' => 12,
77
            'userClassID' => 4, // @todo Rename this settings to swap out "Class" for "Type"
78
            'userGroupClassID' => 3,
79
            'hashType' => User::PASSWORD_HASH_PHP_DEFAULT,
80
            'siteName' => 'ez.no',
81
        );
82
    }
83
84
    /**
85
     * Creates a new user group using the data provided in the ContentCreateStruct parameter.
86
     *
87
     * In 4.x in the content type parameter in the profile is ignored
88
     * - the content type is determined via configuration and can be set to null.
89
     * The returned version is published.
90
     *
91
     * @param \eZ\Publish\API\Repository\Values\User\UserGroupCreateStruct $userGroupCreateStruct a structure for setting all necessary data to create this user group
92
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $parentGroup
93
     *
94
     * @return \eZ\Publish\API\Repository\Values\User\UserGroup
95
     *
96
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to create a user group
97
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the input structure has invalid data
98
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $userGroupCreateStruct is not valid
99
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException if a required field is missing or set to an empty value
100
     */
101
    public function createUserGroup(APIUserGroupCreateStruct $userGroupCreateStruct, APIUserGroup $parentGroup)
102
    {
103
        $contentService = $this->repository->getContentService();
104
        $locationService = $this->repository->getLocationService();
105
        $contentTypeService = $this->repository->getContentTypeService();
106
107
        if ($userGroupCreateStruct->contentType === null) {
108
            $userGroupContentType = $contentTypeService->loadContentType($this->settings['userGroupClassID']);
109
            $userGroupCreateStruct->contentType = $userGroupContentType;
110
        }
111
112
        $loadedParentGroup = $this->loadUserGroup($parentGroup->id);
113
114
        if ($loadedParentGroup->getVersionInfo()->getContentInfo()->mainLocationId === null) {
115
            throw new InvalidArgumentException('parentGroup', 'parent user group has no main location');
116
        }
117
118
        $locationCreateStruct = $locationService->newLocationCreateStruct(
119
            $loadedParentGroup->getVersionInfo()->getContentInfo()->mainLocationId
120
        );
121
122
        $this->repository->beginTransaction();
123
        try {
124
            $contentDraft = $contentService->createContent($userGroupCreateStruct, array($locationCreateStruct));
125
            $publishedContent = $contentService->publishVersion($contentDraft->getVersionInfo());
126
            $this->repository->commit();
127
        } catch (Exception $e) {
128
            $this->repository->rollback();
129
            throw $e;
130
        }
131
132
        return $this->buildDomainUserGroupObject($publishedContent);
133
    }
134
135
    /**
136
     * Loads a user group for the given id.
137
     *
138
     * @param mixed $id
139
     * @param string[] $prioritizedLanguages Used as prioritized language code on translated properties of returned object.
140
     *
141
     * @return \eZ\Publish\API\Repository\Values\User\UserGroup
142
     *
143
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to create a user group
144
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if the user group with the given id was not found
145
     */
146
    public function loadUserGroup($id, array $prioritizedLanguages = [])
147
    {
148
        $content = $this->repository->getContentService()->loadContent($id, $prioritizedLanguages);
149
150
        return $this->buildDomainUserGroupObject($content);
151
    }
152
153
    /**
154
     * Loads the sub groups of a user group.
155
     *
156
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $userGroup
157
     * @param int $offset the start offset for paging
158
     * @param int $limit the number of user groups returned
159
     * @param string[] $prioritizedLanguages Used as prioritized language code on translated properties of returned object.
160
     *
161
     * @return \eZ\Publish\API\Repository\Values\User\UserGroup[]
162
     *
163
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to read the user group
164
     */
165
    public function loadSubUserGroups(APIUserGroup $userGroup, $offset = 0, $limit = 25, array $prioritizedLanguages = [])
166
    {
167
        $locationService = $this->repository->getLocationService();
168
169
        $loadedUserGroup = $this->loadUserGroup($userGroup->id);
170
        if (!$this->repository->canUser('content', 'read', $loadedUserGroup)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\API\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Indicates if the current user is allowed to perform an action given by the function on the given
objects. Example: canUser( 'content', 'edit', $content, $location ); This will check edit permission on content given the specific location, if skipped if will check on all locations. Example2: canUser( 'section', 'assign', $content, $section ); Check if user has access to assign $content to $section.

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

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

Loading history...
171
            throw new UnauthorizedException('content', 'read');
172
        }
173
174
        if ($loadedUserGroup->getVersionInfo()->getContentInfo()->mainLocationId === null) {
175
            return array();
176
        }
177
178
        $mainGroupLocation = $locationService->loadLocation(
179
            $loadedUserGroup->getVersionInfo()->getContentInfo()->mainLocationId
180
        );
181
182
        $searchResult = $this->searchSubGroups($mainGroupLocation, $offset, $limit);
183
        if ($searchResult->totalCount == 0) {
184
            return array();
185
        }
186
187
        $subUserGroups = array();
188 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...
189
            $subUserGroups[] = $this->buildDomainUserGroupObject(
190
                $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...
191
                    $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...
192
                    $prioritizedLanguages
193
                )
194
            );
195
        }
196
197
        return $subUserGroups;
198
    }
199
200
    /**
201
     * Returns (searches) subgroups of a user group described by its main location.
202
     *
203
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
204
     * @param int $offset
205
     * @param int $limit
206
     *
207
     * @return \eZ\Publish\API\Repository\Values\Content\Search\SearchResult
208
     */
209
    protected function searchSubGroups(Location $location, $offset = 0, $limit = 25)
210
    {
211
        $searchQuery = new LocationQuery();
212
213
        $searchQuery->offset = $offset;
214
        $searchQuery->limit = $limit;
215
216
        $searchQuery->filter = new CriterionLogicalAnd([
217
            new CriterionContentTypeId($this->settings['userGroupClassID']),
218
            new CriterionParentLocationId($location->id),
219
        ]);
220
221
        $searchQuery->sortClauses = $location->getSortClauses();
0 ignored issues
show
Documentation Bug introduced by
It seems like $location->getSortClauses() of type array<integer,object,{"0":"object"}> is incompatible with the declared type array<integer,object<eZ\...tent\Query\SortClause>> of property $sortClauses.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
222
223
        return $this->repository->getSearchService()->findLocations($searchQuery, array(), false);
224
    }
225
226
    /**
227
     * Removes a user group.
228
     *
229
     * the users which are not assigned to other groups will be deleted.
230
     *
231
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $userGroup
232
     *
233
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to create a user group
234
     */
235 View Code Duplication
    public function deleteUserGroup(APIUserGroup $userGroup)
236
    {
237
        $loadedUserGroup = $this->loadUserGroup($userGroup->id);
238
239
        $this->repository->beginTransaction();
240
        try {
241
            //@todo: what happens to sub user groups and users below sub user groups
242
            $affectedLocationIds = $this->repository->getContentService()->deleteContent($loadedUserGroup->getVersionInfo()->getContentInfo());
243
            $this->repository->commit();
244
        } catch (Exception $e) {
245
            $this->repository->rollback();
246
            throw $e;
247
        }
248
249
        return $affectedLocationIds;
250
    }
251
252
    /**
253
     * Moves the user group to another parent.
254
     *
255
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $userGroup
256
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $newParent
257
     *
258
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to move the user group
259
     */
260
    public function moveUserGroup(APIUserGroup $userGroup, APIUserGroup $newParent)
261
    {
262
        $loadedUserGroup = $this->loadUserGroup($userGroup->id);
263
        $loadedNewParent = $this->loadUserGroup($newParent->id);
264
265
        $locationService = $this->repository->getLocationService();
266
267
        if ($loadedUserGroup->getVersionInfo()->getContentInfo()->mainLocationId === null) {
268
            throw new BadStateException('userGroup', 'existing user group is not stored and/or does not have any location yet');
269
        }
270
271
        if ($loadedNewParent->getVersionInfo()->getContentInfo()->mainLocationId === null) {
272
            throw new BadStateException('newParent', 'new user group is not stored and/or does not have any location yet');
273
        }
274
275
        $userGroupMainLocation = $locationService->loadLocation(
276
            $loadedUserGroup->getVersionInfo()->getContentInfo()->mainLocationId
277
        );
278
        $newParentMainLocation = $locationService->loadLocation(
279
            $loadedNewParent->getVersionInfo()->getContentInfo()->mainLocationId
280
        );
281
282
        $this->repository->beginTransaction();
283
        try {
284
            $locationService->moveSubtree($userGroupMainLocation, $newParentMainLocation);
285
            $this->repository->commit();
286
        } catch (Exception $e) {
287
            $this->repository->rollback();
288
            throw $e;
289
        }
290
    }
291
292
    /**
293
     * Updates the group profile with fields and meta data.
294
     *
295
     * 4.x: If the versionUpdateStruct is set in $userGroupUpdateStruct, this method internally creates a content draft, updates ts with the provided data
296
     * and publishes the draft. If a draft is explicitly required, the user group can be updated via the content service methods.
297
     *
298
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $userGroup
299
     * @param \eZ\Publish\API\Repository\Values\User\UserGroupUpdateStruct $userGroupUpdateStruct
300
     *
301
     * @return \eZ\Publish\API\Repository\Values\User\UserGroup
302
     *
303
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to update the user group
304
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $userGroupUpdateStruct is not valid
305
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException if a required field is set empty
306
     */
307
    public function updateUserGroup(APIUserGroup $userGroup, UserGroupUpdateStruct $userGroupUpdateStruct)
308
    {
309
        if ($userGroupUpdateStruct->contentUpdateStruct === null &&
310
            $userGroupUpdateStruct->contentMetadataUpdateStruct === null) {
311
            // both update structs are empty, nothing to do
312
            return $userGroup;
313
        }
314
315
        $contentService = $this->repository->getContentService();
316
317
        $loadedUserGroup = $this->loadUserGroup($userGroup->id);
318
319
        $this->repository->beginTransaction();
320
        try {
321
            $publishedContent = $loadedUserGroup;
322 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...
323
                $contentDraft = $contentService->createContentDraft($loadedUserGroup->getVersionInfo()->getContentInfo());
324
325
                $contentDraft = $contentService->updateContent(
326
                    $contentDraft->getVersionInfo(),
327
                    $userGroupUpdateStruct->contentUpdateStruct
328
                );
329
330
                $publishedContent = $contentService->publishVersion($contentDraft->getVersionInfo());
331
            }
332
333
            if ($userGroupUpdateStruct->contentMetadataUpdateStruct !== null) {
334
                $publishedContent = $contentService->updateContentMetadata(
335
                    $publishedContent->getVersionInfo()->getContentInfo(),
336
                    $userGroupUpdateStruct->contentMetadataUpdateStruct
337
                );
338
            }
339
340
            $this->repository->commit();
341
        } catch (Exception $e) {
342
            $this->repository->rollback();
343
            throw $e;
344
        }
345
346
        return $this->buildDomainUserGroupObject($publishedContent);
347
    }
348
349
    /**
350
     * Create a new user. The created user is published by this method.
351
     *
352
     * @param \eZ\Publish\API\Repository\Values\User\UserCreateStruct $userCreateStruct the data used for creating the user
353
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup[] $parentGroups the groups which are assigned to the user after creation
354
     *
355
     * @return \eZ\Publish\API\Repository\Values\User\User
356
     *
357
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to move the user group
358
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $userCreateStruct is not valid
359
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException if a required field is missing or set to an empty value
360
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if a user with provided login already exists
361
     */
362
    public function createUser(APIUserCreateStruct $userCreateStruct, array $parentGroups)
363
    {
364
        if (empty($parentGroups)) {
365
            throw new InvalidArgumentValue('parentGroups', $parentGroups);
366
        }
367
368
        if (!is_string($userCreateStruct->login) || empty($userCreateStruct->login)) {
369
            throw new InvalidArgumentValue('login', $userCreateStruct->login, 'UserCreateStruct');
370
        }
371
372 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...
373
            throw new InvalidArgumentValue('email', $userCreateStruct->email, 'UserCreateStruct');
374
        }
375
376
        if (!preg_match('/^.+@.+\..+$/', $userCreateStruct->email)) {
377
            throw new InvalidArgumentValue('email', $userCreateStruct->email, 'UserCreateStruct');
378
        }
379
380
        if (!is_string($userCreateStruct->password) || empty($userCreateStruct->password)) {
381
            throw new InvalidArgumentValue('password', $userCreateStruct->password, 'UserCreateStruct');
382
        }
383
384
        if (!is_bool($userCreateStruct->enabled)) {
385
            throw new InvalidArgumentValue('enabled', $userCreateStruct->enabled, 'UserCreateStruct');
386
        }
387
388
        try {
389
            $this->userHandler->loadByLogin($userCreateStruct->login);
390
            throw new InvalidArgumentException('userCreateStruct', 'User with provided login already exists');
391
        } catch (NotFoundException $e) {
392
            // Do nothing
393
        }
394
395
        $contentService = $this->repository->getContentService();
396
        $locationService = $this->repository->getLocationService();
397
        $contentTypeService = $this->repository->getContentTypeService();
398
399
        if ($userCreateStruct->contentType === null) {
400
            $userContentType = $contentTypeService->loadContentType($this->settings['userClassID']);
401
            $userCreateStruct->contentType = $userContentType;
402
        }
403
404
        $locationCreateStructs = array();
405
        foreach ($parentGroups as $parentGroup) {
406
            $parentGroup = $this->loadUserGroup($parentGroup->id);
407
            if ($parentGroup->getVersionInfo()->getContentInfo()->mainLocationId !== null) {
408
                $locationCreateStructs[] = $locationService->newLocationCreateStruct(
409
                    $parentGroup->getVersionInfo()->getContentInfo()->mainLocationId
410
                );
411
            }
412
        }
413
414
        // Search for the first ezuser field type in content type
415
        $userFieldDefinition = null;
416
        foreach ($userCreateStruct->contentType->getFieldDefinitions() as $fieldDefinition) {
417
            if ($fieldDefinition->fieldTypeIdentifier == 'ezuser') {
418
                $userFieldDefinition = $fieldDefinition;
419
                break;
420
            }
421
        }
422
423
        if ($userFieldDefinition === null) {
424
            throw new ContentValidationException('Provided content type does not contain ezuser field type');
425
        }
426
427
        $fixUserFieldType = true;
428
        foreach ($userCreateStruct->fields as $index => $field) {
429
            if ($field->fieldDefIdentifier == $userFieldDefinition->identifier) {
430
                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...
431
                    $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...
432
                } else {
433
                    $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...
434
                        array(
435
                            'login' => $userCreateStruct->login,
436
                        )
437
                    );
438
                }
439
440
                $fixUserFieldType = false;
441
            }
442
        }
443
444
        if ($fixUserFieldType) {
445
            $userCreateStruct->setField(
446
                $userFieldDefinition->identifier,
447
                new UserValue(
448
                    array(
449
                        'login' => $userCreateStruct->login,
450
                    )
451
                )
452
            );
453
        }
454
455
        $this->repository->beginTransaction();
456
        try {
457
            $contentDraft = $contentService->createContent($userCreateStruct, $locationCreateStructs);
458
            // Create user before publishing, so that external data can be returned
459
            $spiUser = $this->userHandler->create(
460
                new SPIUser(
461
                    array(
462
                        'id' => $contentDraft->id,
463
                        'login' => $userCreateStruct->login,
464
                        'email' => $userCreateStruct->email,
465
                        'passwordHash' => $this->createPasswordHash(
466
                            $userCreateStruct->login,
467
                            $userCreateStruct->password,
468
                            $this->settings['siteName'],
469
                            $this->settings['hashType']
470
                        ),
471
                        'hashAlgorithm' => $this->settings['hashType'],
472
                        'isEnabled' => $userCreateStruct->enabled,
473
                        'maxLogin' => 0,
474
                    )
475
                )
476
            );
477
            $publishedContent = $contentService->publishVersion($contentDraft->getVersionInfo());
478
479
            $this->repository->commit();
480
        } catch (Exception $e) {
481
            $this->repository->rollback();
482
            throw $e;
483
        }
484
485
        return $this->buildDomainUserObject($spiUser, $publishedContent);
486
    }
487
488
    /**
489
     * Loads a user.
490
     *
491
     * @param mixed $userId
492
     * @param string[] $prioritizedLanguages Used as prioritized language code on translated properties of returned object.
493
     *
494
     * @return \eZ\Publish\API\Repository\Values\User\User
495
     *
496
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if a user with the given id was not found
497
     */
498
    public function loadUser($userId, array $prioritizedLanguages = [])
499
    {
500
        /** @var \eZ\Publish\API\Repository\Values\Content\Content $content */
501
        $content = $this->repository->getContentService()->internalLoadContent($userId, $prioritizedLanguages);
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...
502
        // Get spiUser value from Field Value
503
        foreach ($content->getFields() as $field) {
504
            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...
505
                continue;
506
            }
507
508
            /** @var \eZ\Publish\Core\FieldType\User\Value $value */
509
            $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...
510
            $spiUser = new SPIUser();
511
            $spiUser->id = $value->contentId;
512
            $spiUser->login = $value->login;
513
            $spiUser->email = $value->email;
514
            $spiUser->hashAlgorithm = $value->passwordHashType;
515
            $spiUser->passwordHash = $value->passwordHash;
516
            $spiUser->isEnabled = $value->enabled;
517
            $spiUser->maxLogin = $value->maxLogin;
518
            break;
519
        }
520
521
        // If for some reason not found, load it
522
        if (!isset($spiUser)) {
523
            $spiUser = $this->userHandler->load($userId);
524
        }
525
526
        return $this->buildDomainUserObject($spiUser, $content);
527
    }
528
529
    /**
530
     * Loads anonymous user.
531
     *
532
     * @deprecated since 5.3, use loadUser( $anonymousUserId ) instead
533
     *
534
     * @uses ::loadUser()
535
     *
536
     * @return \eZ\Publish\API\Repository\Values\User\User
537
     */
538
    public function loadAnonymousUser()
539
    {
540
        return $this->loadUser($this->settings['anonymousUserID']);
541
    }
542
543
    /**
544
     * Loads a user for the given login and password.
545
     *
546
     * {@inheritdoc}
547
     *
548
     * @param string $login
549
     * @param string $password the plain password
550
     * @param string[] $prioritizedLanguages Used as prioritized language code on translated properties of returned object.
551
     *
552
     * @return \eZ\Publish\API\Repository\Values\User\User
553
     *
554
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if credentials are invalid
555
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if a user with the given credentials was not found
556
     */
557
    public function loadUserByCredentials($login, $password, array $prioritizedLanguages = [])
558
    {
559
        if (!is_string($login) || empty($login)) {
560
            throw new InvalidArgumentValue('login', $login);
561
        }
562
563
        if (!is_string($password)) {
564
            throw new InvalidArgumentValue('password', $password);
565
        }
566
567
        $spiUser = $this->userHandler->loadByLogin($login);
568
        if (!$this->verifyPassword($login, $password, $spiUser)) {
569
            throw new NotFoundException('user', $login);
570
        }
571
572
        return $this->buildDomainUserObject($spiUser, null, $prioritizedLanguages);
573
    }
574
575
    /**
576
     * Loads a user for the given login.
577
     *
578
     * {@inheritdoc}
579
     *
580
     * @param string $login
581
     * @param string[] $prioritizedLanguages Used as prioritized language code on translated properties of returned object.
582
     *
583
     * @return \eZ\Publish\API\Repository\Values\User\User
584
     *
585
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if a user with the given credentials was not found
586
     */
587
    public function loadUserByLogin($login, array $prioritizedLanguages = [])
588
    {
589
        if (!is_string($login) || empty($login)) {
590
            throw new InvalidArgumentValue('login', $login);
591
        }
592
593
        $spiUser = $this->userHandler->loadByLogin($login);
594
595
        return $this->buildDomainUserObject($spiUser, null, $prioritizedLanguages);
596
    }
597
598
    /**
599
     * Loads a user for the given email.
600
     *
601
     * {@inheritdoc}
602
     *
603
     * @param string $email
604
     * @param string[] $prioritizedLanguages Used as prioritized language code on translated properties of returned object.
605
     *
606
     * @return \eZ\Publish\API\Repository\Values\User\User[]
607
     */
608
    public function loadUsersByEmail($email, array $prioritizedLanguages = [])
609
    {
610
        if (!is_string($email) || empty($email)) {
611
            throw new InvalidArgumentValue('email', $email);
612
        }
613
614
        $users = array();
615
        foreach ($this->userHandler->loadByEmail($email) as $spiUser) {
616
            $users[] = $this->buildDomainUserObject($spiUser, null, $prioritizedLanguages);
617
        }
618
619
        return $users;
620
    }
621
622
    /**
623
     * This method deletes a user.
624
     *
625
     * @param \eZ\Publish\API\Repository\Values\User\User $user
626
     *
627
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to delete the user
628
     */
629 View Code Duplication
    public function deleteUser(APIUser $user)
630
    {
631
        $loadedUser = $this->loadUser($user->id);
632
633
        $this->repository->beginTransaction();
634
        try {
635
            $affectedLocationIds = $this->repository->getContentService()->deleteContent($loadedUser->getVersionInfo()->getContentInfo());
636
            $this->userHandler->delete($loadedUser->id);
637
            $this->repository->commit();
638
        } catch (Exception $e) {
639
            $this->repository->rollback();
640
            throw $e;
641
        }
642
643
        return $affectedLocationIds;
644
    }
645
646
    /**
647
     * Updates a user.
648
     *
649
     * 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
650
     * and publishes the draft. If a draft is explicitly required, the user group can be updated via the content service methods.
651
     *
652
     * @param \eZ\Publish\API\Repository\Values\User\User $user
653
     * @param \eZ\Publish\API\Repository\Values\User\UserUpdateStruct $userUpdateStruct
654
     *
655
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to update the user
656
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $userUpdateStruct is not valid
657
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException if a required field is set empty
658
     *
659
     * @return \eZ\Publish\API\Repository\Values\User\User
660
     */
661
    public function updateUser(APIUser $user, UserUpdateStruct $userUpdateStruct)
662
    {
663
        $loadedUser = $this->loadUser($user->id);
664
665
        // We need to determine if we have anything to update.
666
        // UserUpdateStruct is specific as some of the new content is in
667
        // content update struct and some of it is in additional fields like
668
        // email, password and so on
669
        $doUpdate = false;
670
        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...
671
            if ($propertyValue !== null) {
672
                $doUpdate = true;
673
                break;
674
            }
675
        }
676
677
        if (!$doUpdate) {
678
            // Nothing to update, so we just quit
679
            return $user;
680
        }
681
682
        if ($userUpdateStruct->email !== null) {
683 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...
684
                throw new InvalidArgumentValue('email', $userUpdateStruct->email, 'UserUpdateStruct');
685
            }
686
687
            if (!preg_match('/^.+@.+\..+$/', $userUpdateStruct->email)) {
688
                throw new InvalidArgumentValue('email', $userUpdateStruct->email, 'UserUpdateStruct');
689
            }
690
        }
691
692 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...
693
            throw new InvalidArgumentValue('password', $userUpdateStruct->password, 'UserUpdateStruct');
694
        }
695
696
        if ($userUpdateStruct->enabled !== null && !is_bool($userUpdateStruct->enabled)) {
697
            throw new InvalidArgumentValue('enabled', $userUpdateStruct->enabled, 'UserUpdateStruct');
698
        }
699
700
        if ($userUpdateStruct->maxLogin !== null && !is_int($userUpdateStruct->maxLogin)) {
701
            throw new InvalidArgumentValue('maxLogin', $userUpdateStruct->maxLogin, 'UserUpdateStruct');
702
        }
703
704
        $contentService = $this->repository->getContentService();
705
706
        if (!$this->repository->canUser('content', 'edit', $loadedUser)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\API\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Indicates if the current user is allowed to perform an action given by the function on the given
objects. Example: canUser( 'content', 'edit', $content, $location ); This will check edit permission on content given the specific location, if skipped if will check on all locations. Example2: canUser( 'section', 'assign', $content, $section ); Check if user has access to assign $content to $section.

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

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

Loading history...
707
            throw new UnauthorizedException('content', 'edit');
708
        }
709
710
        $this->repository->beginTransaction();
711
        try {
712
            $publishedContent = $loadedUser;
713 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...
714
                $contentDraft = $contentService->createContentDraft($loadedUser->getVersionInfo()->getContentInfo());
715
                $contentDraft = $contentService->updateContent(
716
                    $contentDraft->getVersionInfo(),
717
                    $userUpdateStruct->contentUpdateStruct
718
                );
719
                $publishedContent = $contentService->publishVersion($contentDraft->getVersionInfo());
720
            }
721
722
            if ($userUpdateStruct->contentMetadataUpdateStruct !== null) {
723
                $contentService->updateContentMetadata(
724
                    $publishedContent->getVersionInfo()->getContentInfo(),
725
                    $userUpdateStruct->contentMetadataUpdateStruct
726
                );
727
            }
728
729
            $this->userHandler->update(
730
                new SPIUser(
731
                    array(
732
                        'id' => $loadedUser->id,
733
                        '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...
734
                        '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...
735
                        'passwordHash' => $userUpdateStruct->password ?
736
                            $this->createPasswordHash(
737
                                $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...
738
                                $userUpdateStruct->password,
739
                                $this->settings['siteName'],
740
                                $this->settings['hashType']
741
                            ) :
742
                            $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...
743
                        'hashAlgorithm' => $this->settings['hashType'],
744
                        '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...
745
                        '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...
746
                    )
747
                )
748
            );
749
750
            $this->repository->commit();
751
        } catch (Exception $e) {
752
            $this->repository->rollback();
753
            throw $e;
754
        }
755
756
        return $this->loadUser($loadedUser->id);
757
    }
758
759
    /**
760
     * Assigns a new user group to the user.
761
     *
762
     * @param \eZ\Publish\API\Repository\Values\User\User $user
763
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $userGroup
764
     *
765
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to assign the user group to the user
766
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the user is already in the given user group
767
     */
768
    public function assignUserToUserGroup(APIUser $user, APIUserGroup $userGroup)
769
    {
770
        $loadedUser = $this->loadUser($user->id);
771
        $loadedGroup = $this->loadUserGroup($userGroup->id);
772
        $locationService = $this->repository->getLocationService();
773
774
        $existingGroupIds = array();
775
        $userLocations = $locationService->loadLocations($loadedUser->getVersionInfo()->getContentInfo());
776
        foreach ($userLocations as $userLocation) {
777
            $existingGroupIds[] = $userLocation->parentLocationId;
778
        }
779
780
        if ($loadedGroup->getVersionInfo()->getContentInfo()->mainLocationId === null) {
781
            throw new BadStateException('userGroup', 'user group has no main location or no locations');
782
        }
783
784
        if (in_array($loadedGroup->getVersionInfo()->getContentInfo()->mainLocationId, $existingGroupIds)) {
785
            // user is already assigned to the user group
786
            throw new InvalidArgumentException('user', 'user is already in the given user group');
787
        }
788
789
        $locationCreateStruct = $locationService->newLocationCreateStruct(
790
            $loadedGroup->getVersionInfo()->getContentInfo()->mainLocationId
791
        );
792
793
        $this->repository->beginTransaction();
794
        try {
795
            $locationService->createLocation(
796
                $loadedUser->getVersionInfo()->getContentInfo(),
797
                $locationCreateStruct
798
            );
799
            $this->repository->commit();
800
        } catch (Exception $e) {
801
            $this->repository->rollback();
802
            throw $e;
803
        }
804
    }
805
806
    /**
807
     * Removes a user group from the user.
808
     *
809
     * @param \eZ\Publish\API\Repository\Values\User\User $user
810
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $userGroup
811
     *
812
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to remove the user group from the user
813
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the user is not in the given user group
814
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException If $userGroup is the last assigned user group
815
     */
816
    public function unAssignUserFromUserGroup(APIUser $user, APIUserGroup $userGroup)
817
    {
818
        $loadedUser = $this->loadUser($user->id);
819
        $loadedGroup = $this->loadUserGroup($userGroup->id);
820
        $locationService = $this->repository->getLocationService();
821
822
        $userLocations = $locationService->loadLocations($loadedUser->getVersionInfo()->getContentInfo());
823
        if (empty($userLocations)) {
824
            throw new BadStateException('user', 'user has no locations, cannot unassign from group');
825
        }
826
827
        if ($loadedGroup->getVersionInfo()->getContentInfo()->mainLocationId === null) {
828
            throw new BadStateException('userGroup', 'user group has no main location or no locations, cannot unassign');
829
        }
830
831
        foreach ($userLocations as $userLocation) {
832
            if ($userLocation->parentLocationId == $loadedGroup->getVersionInfo()->getContentInfo()->mainLocationId) {
833
                // Throw this specific BadState when we know argument is valid
834
                if (count($userLocations) === 1) {
835
                    throw new BadStateException('user', 'user only has one user group, cannot unassign from last group');
836
                }
837
838
                $this->repository->beginTransaction();
839
                try {
840
                    $locationService->deleteLocation($userLocation);
841
                    $this->repository->commit();
842
843
                    return;
844
                } catch (Exception $e) {
845
                    $this->repository->rollback();
846
                    throw $e;
847
                }
848
            }
849
        }
850
851
        throw new InvalidArgumentException('userGroup', 'user is not in the given user group');
852
    }
853
854
    /**
855
     * Loads the user groups the user belongs to.
856
     *
857
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed read the user or user group
858
     *
859
     * @param \eZ\Publish\API\Repository\Values\User\User $user
860
     * @param int $offset the start offset for paging
861
     * @param int $limit the number of user groups returned
862
     * @param string[] $prioritizedLanguages Used as prioritized language code on translated properties of returned object.
863
     *
864
     * @return \eZ\Publish\API\Repository\Values\User\UserGroup[]
865
     */
866
    public function loadUserGroupsOfUser(APIUser $user, $offset = 0, $limit = 25, array $prioritizedLanguages = [])
867
    {
868
        $locationService = $this->repository->getLocationService();
869
870
        if (!$this->repository->getPermissionResolver()->canUser('content', 'read', $user)) {
871
            throw new UnauthorizedException('content', 'read');
872
        }
873
874
        $userLocations = $locationService->loadLocations(
875
            $user->getVersionInfo()->getContentInfo()
876
        );
877
878
        $parentLocationIds = array();
879
        foreach ($userLocations as $userLocation) {
880
            if ($userLocation->parentLocationId !== null) {
881
                $parentLocationIds[] = $userLocation->parentLocationId;
882
            }
883
        }
884
885
        $searchQuery = new LocationQuery();
886
887
        $searchQuery->offset = $offset;
888
        $searchQuery->limit = $limit;
889
        $searchQuery->performCount = false;
890
891
        $searchQuery->filter = new CriterionLogicalAnd(
892
            [
893
                new CriterionContentTypeId($this->settings['userGroupClassID']),
894
                new CriterionLocationId($parentLocationIds),
895
            ]
896
        );
897
898
        $searchResult = $this->repository->getSearchService()->findLocations($searchQuery);
899
900
        $userGroups = [];
901 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...
902
            $userGroups[] = $this->buildDomainUserGroupObject(
903
                $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...
904
                    $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...
905
                    $prioritizedLanguages
906
                )
907
            );
908
        }
909
910
        return $userGroups;
911
    }
912
913
    /**
914
     * Loads the users of a user group.
915
     *
916
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to read the users or user group
917
     *
918
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $userGroup
919
     * @param int $offset the start offset for paging
920
     * @param int $limit the number of users returned
921
     * @param string[] $prioritizedLanguages Used as prioritized language code on translated properties of returned object.
922
     *
923
     * @return \eZ\Publish\API\Repository\Values\User\User[]
924
     */
925
    public function loadUsersOfUserGroup(
926
        APIUserGroup $userGroup,
927
        $offset = 0,
928
        $limit = 25,
929
        array $prioritizedLanguages = []
930
    ) {
931
        $loadedUserGroup = $this->loadUserGroup($userGroup->id);
932
933
        if ($loadedUserGroup->getVersionInfo()->getContentInfo()->mainLocationId === null) {
934
            return [];
935
        }
936
937
        $mainGroupLocation = $this->repository->getLocationService()->loadLocation(
938
            $loadedUserGroup->getVersionInfo()->getContentInfo()->mainLocationId
939
        );
940
941
        $searchQuery = new LocationQuery();
942
943
        $searchQuery->filter = new CriterionLogicalAnd(
944
            [
945
                new CriterionContentTypeId($this->settings['userClassID']),
946
                new CriterionParentLocationId($mainGroupLocation->id),
947
            ]
948
        );
949
950
        $searchQuery->offset = $offset;
951
        $searchQuery->limit = $limit;
952
        $searchQuery->performCount = false;
953
        $searchQuery->sortClauses = $mainGroupLocation->getSortClauses();
0 ignored issues
show
Documentation Bug introduced by
It seems like $mainGroupLocation->getSortClauses() of type array<integer,object,{"0":"object"}> is incompatible with the declared type array<integer,object<eZ\...tent\Query\SortClause>> of property $sortClauses.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
954
955
        $searchResult = $this->repository->getSearchService()->findLocations($searchQuery);
956
957
        $users = [];
958
        foreach ($searchResult->searchHits as $resultItem) {
959
            $users[] = $this->buildDomainUserObject(
960
                $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...
961
                $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...
962
                    $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...
963
                    $prioritizedLanguages
964
                )
965
            );
966
        }
967
968
        return $users;
969
    }
970
971
    /**
972
     * Instantiate a user create class.
973
     *
974
     * @param string $login the login of the new user
975
     * @param string $email the email of the new user
976
     * @param string $password the plain password of the new user
977
     * @param string $mainLanguageCode the main language for the underlying content object
978
     * @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
979
     *
980
     * @return \eZ\Publish\API\Repository\Values\User\UserCreateStruct
981
     */
982
    public function newUserCreateStruct($login, $email, $password, $mainLanguageCode, $contentType = null)
983
    {
984
        if ($contentType === null) {
985
            $contentType = $this->repository->getContentTypeService()->loadContentType(
986
                $this->settings['userClassID']
987
            );
988
        }
989
990
        return new UserCreateStruct(
991
            array(
992
                'contentType' => $contentType,
993
                'mainLanguageCode' => $mainLanguageCode,
994
                'login' => $login,
995
                'email' => $email,
996
                'password' => $password,
997
                'enabled' => true,
998
                'fields' => array(),
999
            )
1000
        );
1001
    }
1002
1003
    /**
1004
     * Instantiate a user group create class.
1005
     *
1006
     * @param string $mainLanguageCode The main language for the underlying content object
1007
     * @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
1008
     *
1009
     * @return \eZ\Publish\API\Repository\Values\User\UserGroupCreateStruct
1010
     */
1011
    public function newUserGroupCreateStruct($mainLanguageCode, $contentType = null)
1012
    {
1013
        if ($contentType === null) {
1014
            $contentType = $this->repository->getContentTypeService()->loadContentType(
1015
                $this->settings['userGroupClassID']
1016
            );
1017
        }
1018
1019
        return new UserGroupCreateStruct(
1020
            array(
1021
                'contentType' => $contentType,
1022
                'mainLanguageCode' => $mainLanguageCode,
1023
                'fields' => array(),
1024
            )
1025
        );
1026
    }
1027
1028
    /**
1029
     * Instantiate a new user update struct.
1030
     *
1031
     * @return \eZ\Publish\API\Repository\Values\User\UserUpdateStruct
1032
     */
1033
    public function newUserUpdateStruct()
1034
    {
1035
        return new UserUpdateStruct();
1036
    }
1037
1038
    /**
1039
     * Instantiate a new user group update struct.
1040
     *
1041
     * @return \eZ\Publish\API\Repository\Values\User\UserGroupUpdateStruct
1042
     */
1043
    public function newUserGroupUpdateStruct()
1044
    {
1045
        return new UserGroupUpdateStruct();
1046
    }
1047
1048
    /**
1049
     * Builds the domain UserGroup object from provided Content object.
1050
     *
1051
     * @param \eZ\Publish\API\Repository\Values\Content\Content $content
1052
     *
1053
     * @return \eZ\Publish\API\Repository\Values\User\UserGroup
1054
     */
1055
    protected function buildDomainUserGroupObject(APIContent $content)
1056
    {
1057
        $locationService = $this->repository->getLocationService();
1058
1059
        $subGroupCount = 0;
1060
        if ($content->getVersionInfo()->getContentInfo()->mainLocationId !== null) {
1061
            $mainLocation = $locationService->loadLocation(
1062
                $content->getVersionInfo()->getContentInfo()->mainLocationId
1063
            );
1064
            $parentLocation = $locationService->loadLocation($mainLocation->parentLocationId);
1065
            $subGroups = $this->searchSubGroups($mainLocation, 0, 0);
1066
            $subGroupCount = $subGroups->totalCount;
1067
        }
1068
1069
        return new UserGroup(
1070
            array(
1071
                'content' => $content,
1072
                'parentId' => isset($parentLocation) ? $parentLocation->contentId : null,
1073
                'subGroupCount' => $subGroupCount,
1074
            )
1075
        );
1076
    }
1077
1078
    /**
1079
     * Builds the domain user object from provided persistence user object.
1080
     *
1081
     * @param \eZ\Publish\SPI\Persistence\User $spiUser
1082
     * @param \eZ\Publish\API\Repository\Values\Content\Content|null $content
1083
     * @param string[] $prioritizedLanguages Used as prioritized language code on translated properties of returned object.
1084
     *
1085
     * @return \eZ\Publish\API\Repository\Values\User\User
1086
     */
1087
    protected function buildDomainUserObject(
1088
        SPIUser $spiUser,
1089
        APIContent $content = null,
1090
        array $prioritizedLanguages = []
1091
    ) {
1092
        if ($content === null) {
1093
            $content = $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...
1094
                $spiUser->id,
1095
                $prioritizedLanguages
1096
            );
1097
        }
1098
1099
        return new User(
1100
            array(
1101
                'content' => $content,
1102
                'login' => $spiUser->login,
1103
                'email' => $spiUser->email,
1104
                'passwordHash' => $spiUser->passwordHash,
1105
                'hashAlgorithm' => (int)$spiUser->hashAlgorithm,
1106
                'enabled' => $spiUser->isEnabled,
1107
                'maxLogin' => (int)$spiUser->maxLogin,
1108
            )
1109
        );
1110
    }
1111
1112
    /**
1113
     * Verifies if the provided login and password are valid.
1114
     *
1115
     * @param string $login User login
1116
     * @param string $password User password
1117
     * @param \eZ\Publish\SPI\Persistence\User $spiUser Loaded user handler
1118
     *
1119
     * @return bool return true if the login and password are sucessfully
1120
     * validate and false, if not.
1121
     */
1122
    protected function verifyPassword($login, $password, $spiUser)
1123
    {
1124
        // In case of bcrypt let php's password functionality do it's magic
1125
        if ($spiUser->hashAlgorithm === User::PASSWORD_HASH_BCRYPT ||
1126
            $spiUser->hashAlgorithm === User::PASSWORD_HASH_PHP_DEFAULT) {
1127
            return password_verify($password, $spiUser->passwordHash);
1128
        }
1129
1130
        // Randomize login time to protect against timing attacks
1131
        usleep(mt_rand(0, 30000));
1132
1133
        $passwordHash = $this->createPasswordHash(
1134
            $login,
1135
            $password,
1136
            $this->settings['siteName'],
1137
            $spiUser->hashAlgorithm
1138
        );
1139
1140
        return $passwordHash === $spiUser->passwordHash;
1141
    }
1142
1143
    /**
1144
     * Returns password hash based on user data and site settings.
1145
     *
1146
     * @param string $login User login
1147
     * @param string $password User password
1148
     * @param string $site The name of the site
1149
     * @param int $type Type of password to generate
1150
     *
1151
     * @return string Generated password hash
1152
     */
1153
    protected function createPasswordHash($login, $password, $site, $type)
1154
    {
1155
        switch ($type) {
1156
            case User::PASSWORD_HASH_MD5_PASSWORD:
1157
                return md5($password);
1158
1159
            case User::PASSWORD_HASH_MD5_USER:
1160
                return md5("$login\n$password");
1161
1162
            case User::PASSWORD_HASH_MD5_SITE:
1163
                return md5("$login\n$password\n$site");
1164
1165
            case User::PASSWORD_HASH_PLAINTEXT:
1166
                return $password;
1167
1168
            case User::PASSWORD_HASH_BCRYPT:
1169
                return password_hash($password, PASSWORD_BCRYPT);
1170
1171
            case User::PASSWORD_HASH_PHP_DEFAULT:
1172
                return password_hash($password, PASSWORD_DEFAULT);
1173
1174
            default:
1175
                return md5($password);
1176
        }
1177
    }
1178
}
1179