Completed
Push — search_feature_flags ( 457ab1...63fd57 )
by André
23:24
created

UserService::createPasswordHash()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 12
nc 5
nop 4
dl 0
loc 19
rs 8.8571
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 ezcMailTools;
40
use Exception;
41
42
/**
43
 * This service provides methods for managing users and user groups.
44
 *
45
 * @example Examples/user.php
46
 */
47
class UserService implements UserServiceInterface
48
{
49
    /**
50
     * @var \eZ\Publish\API\Repository\Repository
51
     */
52
    protected $repository;
53
54
    /**
55
     * @var \eZ\Publish\SPI\Persistence\User\Handler
56
     */
57
    protected $userHandler;
58
59
    /**
60
     * @var array
61
     */
62
    protected $settings;
63
64
    /**
65
     * Setups service with reference to repository object that created it & corresponding handler.
66
     *
67
     * @param \eZ\Publish\API\Repository\Repository $repository
68
     * @param \eZ\Publish\SPI\Persistence\User\Handler $userHandler
69
     * @param array $settings
70
     */
71
    public function __construct(RepositoryInterface $repository, Handler $userHandler, array $settings = array())
72
    {
73
        $this->repository = $repository;
74
        $this->userHandler = $userHandler;
75
        // Union makes sure default settings are ignored if provided in argument
76
        $this->settings = $settings + array(
77
            'defaultUserPlacement' => 12,
78
            'userClassID' => 4, // @todo Rename this settings to swap out "Class" for "Type"
79
            'userGroupClassID' => 3,
80
            'hashType' => User::PASSWORD_HASH_MD5_USER,
81
            'siteName' => 'ez.no',
82
        );
83
    }
84
85
    /**
86
     * Creates a new user group using the data provided in the ContentCreateStruct parameter.
87
     *
88
     * In 4.x in the content type parameter in the profile is ignored
89
     * - the content type is determined via configuration and can be set to null.
90
     * The returned version is published.
91
     *
92
     * @param \eZ\Publish\API\Repository\Values\User\UserGroupCreateStruct $userGroupCreateStruct a structure for setting all necessary data to create this user group
93
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $parentGroup
94
     *
95
     * @return \eZ\Publish\API\Repository\Values\User\UserGroup
96
     *
97
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to create a user group
98
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the input structure has invalid data
99
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $userGroupCreateStruct is not valid
100
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException if a required field is missing or set to an empty value
101
     */
102
    public function createUserGroup(APIUserGroupCreateStruct $userGroupCreateStruct, APIUserGroup $parentGroup)
103
    {
104
        $contentService = $this->repository->getContentService();
105
        $locationService = $this->repository->getLocationService();
106
        $contentTypeService = $this->repository->getContentTypeService();
107
108
        if ($userGroupCreateStruct->contentType === null) {
109
            $userGroupContentType = $contentTypeService->loadContentType($this->settings['userGroupClassID']);
110
            $userGroupCreateStruct->contentType = $userGroupContentType;
111
        }
112
113
        $loadedParentGroup = $this->loadUserGroup($parentGroup->id);
114
115
        if ($loadedParentGroup->getVersionInfo()->getContentInfo()->mainLocationId === null) {
116
            throw new InvalidArgumentException('parentGroup', 'parent user group has no main location');
117
        }
118
119
        $locationCreateStruct = $locationService->newLocationCreateStruct(
120
            $loadedParentGroup->getVersionInfo()->getContentInfo()->mainLocationId
121
        );
122
123
        $this->repository->beginTransaction();
124
        try {
125
            $contentDraft = $contentService->createContent($userGroupCreateStruct, array($locationCreateStruct));
126
            $publishedContent = $contentService->publishVersion($contentDraft->getVersionInfo());
127
            $this->repository->commit();
128
        } catch (Exception $e) {
129
            $this->repository->rollback();
130
            throw $e;
131
        }
132
133
        return $this->buildDomainUserGroupObject($publishedContent);
134
    }
135
136
    /**
137
     * Loads a user group for the given id.
138
     *
139
     * @param mixed $id
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)
147
    {
148
        $content = $this->repository->getContentService()->loadContent($id);
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
     *
160
     * @return \eZ\Publish\API\Repository\Values\User\UserGroup[]
161
     *
162
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to read the user group
163
     */
164
    public function loadSubUserGroups(APIUserGroup $userGroup, $offset = 0, $limit = 25)
165
    {
166
        $locationService = $this->repository->getLocationService();
167
168
        $loadedUserGroup = $this->loadUserGroup($userGroup->id);
169
        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...
170
            throw new UnauthorizedException('content', 'read');
171
        }
172
173
        if ($loadedUserGroup->getVersionInfo()->getContentInfo()->mainLocationId === null) {
174
            return array();
175
        }
176
177
        $mainGroupLocation = $locationService->loadLocation(
178
            $loadedUserGroup->getVersionInfo()->getContentInfo()->mainLocationId
179
        );
180
181
        $searchResult = $this->searchSubGroups($mainGroupLocation, $offset, $limit);
182
        if ($searchResult->totalCount == 0) {
183
            return array();
184
        }
185
186
        $subUserGroups = array();
187 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...
188
            $subUserGroups[] = $this->buildDomainUserGroupObject(
189
                $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...
190
                    $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...
191
                )
192
            );
193
        }
194
195
        return $subUserGroups;
196
    }
197
198
    /**
199
     * Returns (searches) subgroups of a user group described by its main location.
200
     *
201
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
202
     * @param int $offset
203
     * @param int $limit
204
     *
205
     * @return \eZ\Publish\API\Repository\Values\Content\Search\SearchResult
206
     */
207
    protected function searchSubGroups(Location $location, $offset = 0, $limit = 25)
208
    {
209
        $searchQuery = new LocationQuery();
210
211
        $searchQuery->offset = $offset;
212
        $searchQuery->limit = $limit;
213
214
        $searchQuery->filter = new CriterionLogicalAnd([
215
            new CriterionContentTypeId($this->settings['userGroupClassID']),
216
            new CriterionParentLocationId($location->id),
217
        ]);
218
219
        $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...
220
221
        return $this->repository->getSearchService()->findLocations($searchQuery, array(), false);
222
    }
223
224
    /**
225
     * Removes a user group.
226
     *
227
     * the users which are not assigned to other groups will be deleted.
228
     *
229
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $userGroup
230
     *
231
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to create a user group
232
     */
233 View Code Duplication
    public function deleteUserGroup(APIUserGroup $userGroup)
234
    {
235
        $loadedUserGroup = $this->loadUserGroup($userGroup->id);
236
237
        $this->repository->beginTransaction();
238
        try {
239
            //@todo: what happens to sub user groups and users below sub user groups
240
            $affectedLocationIds = $this->repository->getContentService()->deleteContent($loadedUserGroup->getVersionInfo()->getContentInfo());
241
            $this->repository->commit();
242
        } catch (Exception $e) {
243
            $this->repository->rollback();
244
            throw $e;
245
        }
246
247
        return $affectedLocationIds;
248
    }
249
250
    /**
251
     * Moves the user group to another parent.
252
     *
253
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $userGroup
254
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $newParent
255
     *
256
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to move the user group
257
     */
258
    public function moveUserGroup(APIUserGroup $userGroup, APIUserGroup $newParent)
259
    {
260
        $loadedUserGroup = $this->loadUserGroup($userGroup->id);
261
        $loadedNewParent = $this->loadUserGroup($newParent->id);
262
263
        $locationService = $this->repository->getLocationService();
264
265
        if ($loadedUserGroup->getVersionInfo()->getContentInfo()->mainLocationId === null) {
266
            throw new BadStateException('userGroup', 'existing user group is not stored and/or does not have any location yet');
267
        }
268
269
        if ($loadedNewParent->getVersionInfo()->getContentInfo()->mainLocationId === null) {
270
            throw new BadStateException('newParent', 'new user group is not stored and/or does not have any location yet');
271
        }
272
273
        $userGroupMainLocation = $locationService->loadLocation(
274
            $loadedUserGroup->getVersionInfo()->getContentInfo()->mainLocationId
275
        );
276
        $newParentMainLocation = $locationService->loadLocation(
277
            $loadedNewParent->getVersionInfo()->getContentInfo()->mainLocationId
278
        );
279
280
        $this->repository->beginTransaction();
281
        try {
282
            $locationService->moveSubtree($userGroupMainLocation, $newParentMainLocation);
283
            $this->repository->commit();
284
        } catch (Exception $e) {
285
            $this->repository->rollback();
286
            throw $e;
287
        }
288
    }
289
290
    /**
291
     * Updates the group profile with fields and meta data.
292
     *
293
     * 4.x: If the versionUpdateStruct is set in $userGroupUpdateStruct, this method internally creates a content draft, updates ts with the provided data
294
     * and publishes the draft. If a draft is explicitly required, the user group can be updated via the content service methods.
295
     *
296
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $userGroup
297
     * @param \eZ\Publish\API\Repository\Values\User\UserGroupUpdateStruct $userGroupUpdateStruct
298
     *
299
     * @return \eZ\Publish\API\Repository\Values\User\UserGroup
300
     *
301
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to update the user group
302
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $userGroupUpdateStruct is not valid
303
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException if a required field is set empty
304
     */
305
    public function updateUserGroup(APIUserGroup $userGroup, UserGroupUpdateStruct $userGroupUpdateStruct)
306
    {
307
        if ($userGroupUpdateStruct->contentUpdateStruct === null &&
308
            $userGroupUpdateStruct->contentMetadataUpdateStruct === null) {
309
            // both update structs are empty, nothing to do
310
            return $userGroup;
311
        }
312
313
        $contentService = $this->repository->getContentService();
314
315
        $loadedUserGroup = $this->loadUserGroup($userGroup->id);
316
317
        $this->repository->beginTransaction();
318
        try {
319
            $publishedContent = $loadedUserGroup;
320 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...
321
                $contentDraft = $contentService->createContentDraft($loadedUserGroup->getVersionInfo()->getContentInfo());
322
323
                $contentDraft = $contentService->updateContent(
324
                    $contentDraft->getVersionInfo(),
325
                    $userGroupUpdateStruct->contentUpdateStruct
326
                );
327
328
                $publishedContent = $contentService->publishVersion($contentDraft->getVersionInfo());
329
            }
330
331
            if ($userGroupUpdateStruct->contentMetadataUpdateStruct !== null) {
332
                $publishedContent = $contentService->updateContentMetadata(
333
                    $publishedContent->getVersionInfo()->getContentInfo(),
334
                    $userGroupUpdateStruct->contentMetadataUpdateStruct
335
                );
336
            }
337
338
            $this->repository->commit();
339
        } catch (Exception $e) {
340
            $this->repository->rollback();
341
            throw $e;
342
        }
343
344
        return $this->buildDomainUserGroupObject($publishedContent);
345
    }
346
347
    /**
348
     * Create a new user. The created user is published by this method.
349
     *
350
     * @param \eZ\Publish\API\Repository\Values\User\UserCreateStruct $userCreateStruct the data used for creating the user
351
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup[] $parentGroups the groups which are assigned to the user after creation
352
     *
353
     * @return \eZ\Publish\API\Repository\Values\User\User
354
     *
355
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to move the user group
356
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $userCreateStruct is not valid
357
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException if a required field is missing or set to an empty value
358
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if a user with provided login already exists
359
     */
360
    public function createUser(APIUserCreateStruct $userCreateStruct, array $parentGroups)
361
    {
362
        if (empty($parentGroups)) {
363
            throw new InvalidArgumentValue('parentGroups', $parentGroups);
364
        }
365
366
        if (!is_string($userCreateStruct->login) || empty($userCreateStruct->login)) {
367
            throw new InvalidArgumentValue('login', $userCreateStruct->login, 'UserCreateStruct');
368
        }
369
370 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...
371
            throw new InvalidArgumentValue('email', $userCreateStruct->email, 'UserCreateStruct');
372
        }
373
374
        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...
375
            throw new InvalidArgumentValue('email', $userCreateStruct->email, 'UserCreateStruct');
376
        }
377
378
        if (!is_string($userCreateStruct->password) || empty($userCreateStruct->password)) {
379
            throw new InvalidArgumentValue('password', $userCreateStruct->password, 'UserCreateStruct');
380
        }
381
382
        if (!is_bool($userCreateStruct->enabled)) {
383
            throw new InvalidArgumentValue('enabled', $userCreateStruct->enabled, 'UserCreateStruct');
384
        }
385
386
        try {
387
            $this->userHandler->loadByLogin($userCreateStruct->login);
388
            throw new InvalidArgumentException('userCreateStruct', 'User with provided login already exists');
389
        } catch (NotFoundException $e) {
390
            // Do nothing
391
        }
392
393
        $contentService = $this->repository->getContentService();
394
        $locationService = $this->repository->getLocationService();
395
        $contentTypeService = $this->repository->getContentTypeService();
396
397
        if ($userCreateStruct->contentType === null) {
398
            $userContentType = $contentTypeService->loadContentType($this->settings['userClassID']);
399
            $userCreateStruct->contentType = $userContentType;
400
        }
401
402
        $locationCreateStructs = array();
403
        foreach ($parentGroups as $parentGroup) {
404
            $parentGroup = $this->loadUserGroup($parentGroup->id);
405
            if ($parentGroup->getVersionInfo()->getContentInfo()->mainLocationId !== null) {
406
                $locationCreateStructs[] = $locationService->newLocationCreateStruct(
407
                    $parentGroup->getVersionInfo()->getContentInfo()->mainLocationId
408
                );
409
            }
410
        }
411
412
        // Search for the first ezuser field type in content type
413
        $userFieldDefinition = null;
414
        foreach ($userCreateStruct->contentType->getFieldDefinitions() as $fieldDefinition) {
415
            if ($fieldDefinition->fieldTypeIdentifier == 'ezuser') {
416
                $userFieldDefinition = $fieldDefinition;
417
                break;
418
            }
419
        }
420
421
        if ($userFieldDefinition === null) {
422
            throw new ContentValidationException('Provided content type does not contain ezuser field type');
423
        }
424
425
        $fixUserFieldType = true;
426
        foreach ($userCreateStruct->fields as $index => $field) {
427
            if ($field->fieldDefIdentifier == $userFieldDefinition->identifier) {
428
                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...
429
                    $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...
430
                } else {
431
                    $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...
432
                        array(
433
                            'login' => $userCreateStruct->login,
434
                        )
435
                    );
436
                }
437
438
                $fixUserFieldType = false;
439
            }
440
        }
441
442
        if ($fixUserFieldType) {
443
            $userCreateStruct->setField(
444
                $userFieldDefinition->identifier,
445
                new UserValue(
446
                    array(
447
                        'login' => $userCreateStruct->login,
448
                    )
449
                )
450
            );
451
        }
452
453
        $this->repository->beginTransaction();
454
        try {
455
            $contentDraft = $contentService->createContent($userCreateStruct, $locationCreateStructs);
456
            // Create user before publishing, so that external data can be returned
457
            $spiUser = $this->userHandler->create(
458
                new SPIUser(
459
                    array(
460
                        'id' => $contentDraft->id,
461
                        'login' => $userCreateStruct->login,
462
                        'email' => $userCreateStruct->email,
463
                        'passwordHash' => $this->createPasswordHash(
464
                            $userCreateStruct->login,
465
                            $userCreateStruct->password,
466
                            $this->settings['siteName'],
467
                            $this->settings['hashType']
468
                        ),
469
                        'hashAlgorithm' => $this->settings['hashType'],
470
                        'isEnabled' => $userCreateStruct->enabled,
471
                        'maxLogin' => 0,
472
                    )
473
                )
474
            );
475
            $publishedContent = $contentService->publishVersion($contentDraft->getVersionInfo());
476
477
            $this->repository->commit();
478
        } catch (Exception $e) {
479
            $this->repository->rollback();
480
            throw $e;
481
        }
482
483
        return $this->buildDomainUserObject($spiUser, $publishedContent);
484
    }
485
486
    /**
487
     * Loads a user.
488
     *
489
     * @param mixed $userId
490
     *
491
     * @return \eZ\Publish\API\Repository\Values\User\User
492
     *
493
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if a user with the given id was not found
494
     */
495
    public function loadUser($userId)
496
    {
497
        /** @var \eZ\Publish\API\Repository\Values\Content\Content $content */
498
        $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...
499
        // Get spiUser value from Field Value
500
        foreach ($content->getFields() as $field) {
501
            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...
502
                continue;
503
            }
504
505
            /** @var \eZ\Publish\Core\FieldType\User\Value $value */
506
            $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...
507
            $spiUser = new SPIUser();
508
            $spiUser->id = $value->contentId;
509
            $spiUser->login = $value->login;
510
            $spiUser->email = $value->email;
511
            $spiUser->hashAlgorithm = $value->passwordHashType;
512
            $spiUser->passwordHash = $value->passwordHash;
513
            $spiUser->isEnabled = $value->enabled;
514
            $spiUser->maxLogin = $value->maxLogin;
515
            break;
516
        }
517
518
        // If for some reason not found, load it
519
        if (!isset($spiUser)) {
520
            $spiUser = $this->userHandler->load($userId);
521
        }
522
523
        return $this->buildDomainUserObject($spiUser, $content);
524
    }
525
526
    /**
527
     * Loads anonymous user.
528
     *
529
     * @deprecated since 5.3, use loadUser( $anonymousUserId ) instead
530
     *
531
     * @uses ::loadUser()
532
     *
533
     * @return \eZ\Publish\API\Repository\Values\User\User
534
     */
535
    public function loadAnonymousUser()
536
    {
537
        return $this->loadUser($this->settings['anonymousUserID']);
538
    }
539
540
    /**
541
     * Loads a user for the given login and password.
542
     *
543
     * {@inheritdoc}
544
     *
545
     * @param string $login
546
     * @param string $password the plain password
547
     *
548
     * @return \eZ\Publish\API\Repository\Values\User\User
549
     *
550
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if credentials are invalid
551
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if a user with the given credentials was not found
552
     */
553
    public function loadUserByCredentials($login, $password)
554
    {
555
        if (!is_string($login) || empty($login)) {
556
            throw new InvalidArgumentValue('login', $login);
557
        }
558
559
        if (!is_string($password)) {
560
            throw new InvalidArgumentValue('password', $password);
561
        }
562
563
        // Randomize login time to protect against timing attacks
564
        usleep(mt_rand(0, 30000));
565
566
        $spiUser = $this->userHandler->loadByLogin($login);
567
        $passwordHash = $this->createPasswordHash(
568
            $login,
569
            $password,
570
            $this->settings['siteName'],
571
            $spiUser->hashAlgorithm
572
        );
573
574
        if ($spiUser->passwordHash !== $passwordHash) {
575
            throw new NotFoundException('user', $login);
576
        }
577
578
        return $this->buildDomainUserObject($spiUser);
579
    }
580
581
    /**
582
     * Loads a user for the given login.
583
     *
584
     * {@inheritdoc}
585
     *
586
     * @param string $login
587
     *
588
     * @return \eZ\Publish\API\Repository\Values\User\User
589
     *
590
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if a user with the given credentials was not found
591
     */
592
    public function loadUserByLogin($login)
593
    {
594
        if (!is_string($login) || empty($login)) {
595
            throw new InvalidArgumentValue('login', $login);
596
        }
597
598
        $spiUser = $this->userHandler->loadByLogin($login);
599
600
        return $this->buildDomainUserObject($spiUser);
601
    }
602
603
    /**
604
     * Loads a user for the given email.
605
     *
606
     * {@inheritdoc}
607
     *
608
     * @param string $email
609
     *
610
     * @return \eZ\Publish\API\Repository\Values\User\User[]
611
     */
612
    public function loadUsersByEmail($email)
613
    {
614
        if (!is_string($email) || empty($email)) {
615
            throw new InvalidArgumentValue('email', $email);
616
        }
617
618
        $users = array();
619
        foreach ($this->userHandler->loadByEmail($email) as $spiUser) {
620
            $users[] = $this->buildDomainUserObject($spiUser);
621
        }
622
623
        return $users;
624
    }
625
626
    /**
627
     * This method deletes a user.
628
     *
629
     * @param \eZ\Publish\API\Repository\Values\User\User $user
630
     *
631
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to delete the user
632
     */
633 View Code Duplication
    public function deleteUser(APIUser $user)
634
    {
635
        $loadedUser = $this->loadUser($user->id);
636
637
        $this->repository->beginTransaction();
638
        try {
639
            $affectedLocationIds = $this->repository->getContentService()->deleteContent($loadedUser->getVersionInfo()->getContentInfo());
640
            $this->userHandler->delete($loadedUser->id);
641
            $this->repository->commit();
642
        } catch (Exception $e) {
643
            $this->repository->rollback();
644
            throw $e;
645
        }
646
647
        return $affectedLocationIds;
648
    }
649
650
    /**
651
     * Updates a user.
652
     *
653
     * 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
654
     * and publishes the draft. If a draft is explicitly required, the user group can be updated via the content service methods.
655
     *
656
     * @param \eZ\Publish\API\Repository\Values\User\User $user
657
     * @param \eZ\Publish\API\Repository\Values\User\UserUpdateStruct $userUpdateStruct
658
     *
659
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to update the user
660
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $userUpdateStruct is not valid
661
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException if a required field is set empty
662
     *
663
     * @return \eZ\Publish\API\Repository\Values\User\User
664
     */
665
    public function updateUser(APIUser $user, UserUpdateStruct $userUpdateStruct)
666
    {
667
        $loadedUser = $this->loadUser($user->id);
668
669
        // We need to determine if we have anything to update.
670
        // UserUpdateStruct is specific as some of the new content is in
671
        // content update struct and some of it is in additional fields like
672
        // email, password and so on
673
        $doUpdate = false;
674
        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...
675
            if ($propertyValue !== null) {
676
                $doUpdate = true;
677
                break;
678
            }
679
        }
680
681
        if (!$doUpdate) {
682
            // Nothing to update, so we just quit
683
            return $user;
684
        }
685
686
        if ($userUpdateStruct->email !== null) {
687 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...
688
                throw new InvalidArgumentValue('email', $userUpdateStruct->email, 'UserUpdateStruct');
689
            }
690
691
            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...
692
                throw new InvalidArgumentValue('email', $userUpdateStruct->email, 'UserUpdateStruct');
693
            }
694
        }
695
696 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...
697
            throw new InvalidArgumentValue('password', $userUpdateStruct->password, 'UserUpdateStruct');
698
        }
699
700
        if ($userUpdateStruct->enabled !== null && !is_bool($userUpdateStruct->enabled)) {
701
            throw new InvalidArgumentValue('enabled', $userUpdateStruct->enabled, 'UserUpdateStruct');
702
        }
703
704
        if ($userUpdateStruct->maxLogin !== null && !is_int($userUpdateStruct->maxLogin)) {
705
            throw new InvalidArgumentValue('maxLogin', $userUpdateStruct->maxLogin, 'UserUpdateStruct');
706
        }
707
708
        $contentService = $this->repository->getContentService();
709
710
        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...
711
            throw new UnauthorizedException('content', 'edit');
712
        }
713
714
        $this->repository->beginTransaction();
715
        try {
716
            $publishedContent = $loadedUser;
717 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...
718
                $contentDraft = $contentService->createContentDraft($loadedUser->getVersionInfo()->getContentInfo());
719
                $contentDraft = $contentService->updateContent(
720
                    $contentDraft->getVersionInfo(),
721
                    $userUpdateStruct->contentUpdateStruct
722
                );
723
                $publishedContent = $contentService->publishVersion($contentDraft->getVersionInfo());
724
            }
725
726
            if ($userUpdateStruct->contentMetadataUpdateStruct !== null) {
727
                $contentService->updateContentMetadata(
728
                    $publishedContent->getVersionInfo()->getContentInfo(),
729
                    $userUpdateStruct->contentMetadataUpdateStruct
730
                );
731
            }
732
733
            $this->userHandler->update(
734
                new SPIUser(
735
                    array(
736
                        'id' => $loadedUser->id,
737
                        '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...
738
                        '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...
739
                        'passwordHash' => $userUpdateStruct->password ?
740
                            $this->createPasswordHash(
741
                                $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...
742
                                $userUpdateStruct->password,
743
                                $this->settings['siteName'],
744
                                $this->settings['hashType']
745
                            ) :
746
                            $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...
747
                        'hashAlgorithm' => $this->settings['hashType'],
748
                        '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...
749
                        '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...
750
                    )
751
                )
752
            );
753
754
            $this->repository->commit();
755
        } catch (Exception $e) {
756
            $this->repository->rollback();
757
            throw $e;
758
        }
759
760
        return $this->loadUser($loadedUser->id);
761
    }
762
763
    /**
764
     * Assigns a new user group to the user.
765
     *
766
     * @param \eZ\Publish\API\Repository\Values\User\User $user
767
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $userGroup
768
     *
769
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to assign the user group to the user
770
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the user is already in the given user group
771
     */
772
    public function assignUserToUserGroup(APIUser $user, APIUserGroup $userGroup)
773
    {
774
        $loadedUser = $this->loadUser($user->id);
775
        $loadedGroup = $this->loadUserGroup($userGroup->id);
776
        $locationService = $this->repository->getLocationService();
777
778
        $existingGroupIds = array();
779
        $userLocations = $locationService->loadLocations($loadedUser->getVersionInfo()->getContentInfo());
780
        foreach ($userLocations as $userLocation) {
781
            $existingGroupIds[] = $userLocation->parentLocationId;
782
        }
783
784
        if ($loadedGroup->getVersionInfo()->getContentInfo()->mainLocationId === null) {
785
            throw new BadStateException('userGroup', 'user group has no main location or no locations');
786
        }
787
788
        if (in_array($loadedGroup->getVersionInfo()->getContentInfo()->mainLocationId, $existingGroupIds)) {
789
            // user is already assigned to the user group
790
            throw new InvalidArgumentException('user', 'user is already in the given user group');
791
        }
792
793
        $locationCreateStruct = $locationService->newLocationCreateStruct(
794
            $loadedGroup->getVersionInfo()->getContentInfo()->mainLocationId
795
        );
796
797
        $this->repository->beginTransaction();
798
        try {
799
            $locationService->createLocation(
800
                $loadedUser->getVersionInfo()->getContentInfo(),
801
                $locationCreateStruct
802
            );
803
            $this->repository->commit();
804
        } catch (Exception $e) {
805
            $this->repository->rollback();
806
            throw $e;
807
        }
808
    }
809
810
    /**
811
     * Removes a user group from the user.
812
     *
813
     * @param \eZ\Publish\API\Repository\Values\User\User $user
814
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $userGroup
815
     *
816
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to remove the user group from the user
817
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the user is not in the given user group
818
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException If $userGroup is the last assigned user group
819
     */
820
    public function unAssignUserFromUserGroup(APIUser $user, APIUserGroup $userGroup)
821
    {
822
        $loadedUser = $this->loadUser($user->id);
823
        $loadedGroup = $this->loadUserGroup($userGroup->id);
824
        $locationService = $this->repository->getLocationService();
825
826
        $userLocations = $locationService->loadLocations($loadedUser->getVersionInfo()->getContentInfo());
827
        if (empty($userLocations)) {
828
            throw new BadStateException('user', 'user has no locations, cannot unassign from group');
829
        }
830
831
        if ($loadedGroup->getVersionInfo()->getContentInfo()->mainLocationId === null) {
832
            throw new BadStateException('userGroup', 'user group has no main location or no locations, cannot unassign');
833
        }
834
835
        foreach ($userLocations as $userLocation) {
836
            if ($userLocation->parentLocationId == $loadedGroup->getVersionInfo()->getContentInfo()->mainLocationId) {
837
                // Throw this specific BadState when we know argument is valid
838
                if (count($userLocations) === 1) {
839
                    throw new BadStateException('user', 'user only has one user group, cannot unassign from last group');
840
                }
841
842
                $this->repository->beginTransaction();
843
                try {
844
                    $locationService->deleteLocation($userLocation);
845
                    $this->repository->commit();
846
847
                    return;
848
                } catch (Exception $e) {
849
                    $this->repository->rollback();
850
                    throw $e;
851
                }
852
            }
853
        }
854
855
        throw new InvalidArgumentException('userGroup', 'user is not in the given user group');
856
    }
857
858
    /**
859
     * Loads the user groups the user belongs to.
860
     *
861
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed read the user or user group
862
     *
863
     * @param \eZ\Publish\API\Repository\Values\User\User $user
864
     * @param int $offset the start offset for paging
865
     * @param int $limit the number of user groups returned
866
     *
867
     * @return \eZ\Publish\API\Repository\Values\User\UserGroup[]
868
     */
869
    public function loadUserGroupsOfUser(APIUser $user, $offset = 0, $limit = 25)
870
    {
871
        $locationService = $this->repository->getLocationService();
872
873
        if (!$this->repository->canUser('content', 'read', $user)) {
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...
874
            throw new UnauthorizedException('content', 'read');
875
        }
876
877
        $userLocations = $locationService->loadLocations(
878
            $user->getVersionInfo()->getContentInfo()
879
        );
880
881
        $parentLocationIds = array();
882
        foreach ($userLocations as $userLocation) {
883
            if ($userLocation->parentLocationId !== null) {
884
                $parentLocationIds[] = $userLocation->parentLocationId;
885
            }
886
        }
887
888
        $searchQuery = new LocationQuery();
889
890
        $searchQuery->offset = $offset;
891
        $searchQuery->limit = $limit;
892
        $searchQuery->performCount = false;
893
894
        $searchQuery->filter = new CriterionLogicalAnd(
895
            array(
896
                new CriterionContentTypeId($this->settings['userGroupClassID']),
897
                new CriterionLocationId($parentLocationIds),
898
            )
899
        );
900
901
        $searchResult = $this->repository->getSearchService()->findLocations($searchQuery);
902
903
        $userGroups = array();
904 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...
905
            $userGroups[] = $this->buildDomainUserGroupObject(
906
                $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...
907
                    $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...
908
                )
909
            );
910
        }
911
912
        return $userGroups;
913
    }
914
915
    /**
916
     * Loads the users of a user group.
917
     *
918
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to read the users or user group
919
     *
920
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $userGroup
921
     * @param int $offset the start offset for paging
922
     * @param int $limit the number of users returned
923
     *
924
     * @return \eZ\Publish\API\Repository\Values\User\User[]
925
     */
926
    public function loadUsersOfUserGroup(APIUserGroup $userGroup, $offset = 0, $limit = 25)
927
    {
928
        $loadedUserGroup = $this->loadUserGroup($userGroup->id);
929
930
        if ($loadedUserGroup->getVersionInfo()->getContentInfo()->mainLocationId === null) {
931
            return array();
932
        }
933
934
        $mainGroupLocation = $this->repository->getLocationService()->loadLocation(
935
            $loadedUserGroup->getVersionInfo()->getContentInfo()->mainLocationId
936
        );
937
938
        $searchQuery = new LocationQuery();
939
940
        $searchQuery->filter = new CriterionLogicalAnd(
941
            array(
942
                new CriterionContentTypeId($this->settings['userClassID']),
943
                new CriterionParentLocationId($mainGroupLocation->id),
944
            )
945
        );
946
947
        $searchQuery->offset = $offset;
948
        $searchQuery->limit = $limit;
949
        $searchQuery->performCount = false;
950
        $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...
951
952
        $searchResult = $this->repository->getSearchService()->findLocations($searchQuery);
953
954
        $users = array();
955
        foreach ($searchResult->searchHits as $resultItem) {
956
            $users[] = $this->buildDomainUserObject(
957
                $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...
958
                $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...
959
                    $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...
960
                )
961
            );
962
        }
963
964
        return $users;
965
    }
966
967
    /**
968
     * Instantiate a user create class.
969
     *
970
     * @param string $login the login of the new user
971
     * @param string $email the email of the new user
972
     * @param string $password the plain password of the new user
973
     * @param string $mainLanguageCode the main language for the underlying content object
974
     * @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
975
     *
976
     * @return \eZ\Publish\API\Repository\Values\User\UserCreateStruct
977
     */
978
    public function newUserCreateStruct($login, $email, $password, $mainLanguageCode, $contentType = null)
979
    {
980
        if ($contentType === null) {
981
            $contentType = $this->repository->getContentTypeService()->loadContentType(
982
                $this->settings['userClassID']
983
            );
984
        }
985
986
        return new UserCreateStruct(
987
            array(
988
                'contentType' => $contentType,
989
                'mainLanguageCode' => $mainLanguageCode,
990
                'login' => $login,
991
                'email' => $email,
992
                'password' => $password,
993
                'enabled' => true,
994
                'fields' => array(),
995
            )
996
        );
997
    }
998
999
    /**
1000
     * Instantiate a user group create class.
1001
     *
1002
     * @param string $mainLanguageCode The main language for the underlying content object
1003
     * @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
1004
     *
1005
     * @return \eZ\Publish\API\Repository\Values\User\UserGroupCreateStruct
1006
     */
1007
    public function newUserGroupCreateStruct($mainLanguageCode, $contentType = null)
1008
    {
1009
        if ($contentType === null) {
1010
            $contentType = $this->repository->getContentTypeService()->loadContentType(
1011
                $this->settings['userGroupClassID']
1012
            );
1013
        }
1014
1015
        return new UserGroupCreateStruct(
1016
            array(
1017
                'contentType' => $contentType,
1018
                'mainLanguageCode' => $mainLanguageCode,
1019
                'fields' => array(),
1020
            )
1021
        );
1022
    }
1023
1024
    /**
1025
     * Instantiate a new user update struct.
1026
     *
1027
     * @return \eZ\Publish\API\Repository\Values\User\UserUpdateStruct
1028
     */
1029
    public function newUserUpdateStruct()
1030
    {
1031
        return new UserUpdateStruct();
1032
    }
1033
1034
    /**
1035
     * Instantiate a new user group update struct.
1036
     *
1037
     * @return \eZ\Publish\API\Repository\Values\User\UserGroupUpdateStruct
1038
     */
1039
    public function newUserGroupUpdateStruct()
1040
    {
1041
        return new UserGroupUpdateStruct();
1042
    }
1043
1044
    /**
1045
     * Builds the domain UserGroup object from provided Content object.
1046
     *
1047
     * @param \eZ\Publish\API\Repository\Values\Content\Content $content
1048
     *
1049
     * @return \eZ\Publish\API\Repository\Values\User\UserGroup
1050
     */
1051
    protected function buildDomainUserGroupObject(APIContent $content)
1052
    {
1053
        $locationService = $this->repository->getLocationService();
1054
1055
        $subGroupCount = 0;
1056
        if ($content->getVersionInfo()->getContentInfo()->mainLocationId !== null) {
1057
            $mainLocation = $locationService->loadLocation(
1058
                $content->getVersionInfo()->getContentInfo()->mainLocationId
1059
            );
1060
            $parentLocation = $locationService->loadLocation($mainLocation->parentLocationId);
1061
            $subGroups = $this->searchSubGroups($mainLocation, 0, 0);
1062
            $subGroupCount = $subGroups->totalCount;
1063
        }
1064
1065
        return new UserGroup(
1066
            array(
1067
                'content' => $content,
1068
                'parentId' => isset($parentLocation) ? $parentLocation->contentId : null,
1069
                'subGroupCount' => $subGroupCount,
1070
            )
1071
        );
1072
    }
1073
1074
    /**
1075
     * Builds the domain user object from provided persistence user object.
1076
     *
1077
     * @param \eZ\Publish\SPI\Persistence\User $spiUser
1078
     * @param \eZ\Publish\API\Repository\Values\Content\Content|null $content
1079
     *
1080
     * @return \eZ\Publish\API\Repository\Values\User\User
1081
     */
1082
    protected function buildDomainUserObject(SPIUser $spiUser, APIContent $content = null)
1083
    {
1084
        if ($content === null) {
1085
            $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...
1086
        }
1087
1088
        return new User(
1089
            array(
1090
                'content' => $content,
1091
                'login' => $spiUser->login,
1092
                'email' => $spiUser->email,
1093
                'passwordHash' => $spiUser->passwordHash,
1094
                'hashAlgorithm' => (int)$spiUser->hashAlgorithm,
1095
                'enabled' => $spiUser->isEnabled,
1096
                'maxLogin' => (int)$spiUser->maxLogin,
1097
            )
1098
        );
1099
    }
1100
1101
    /**
1102
     * Returns password hash based on user data and site settings.
1103
     *
1104
     * @param string $login User login
1105
     * @param string $password User password
1106
     * @param string $site The name of the site
1107
     * @param int $type Type of password to generate
1108
     *
1109
     * @return string Generated password hash
1110
     */
1111
    protected function createPasswordHash($login, $password, $site, $type)
1112
    {
1113
        switch ($type) {
1114
            case User::PASSWORD_HASH_MD5_PASSWORD:
1115
                return md5($password);
1116
1117
            case User::PASSWORD_HASH_MD5_USER:
1118
                return md5("$login\n$password");
1119
1120
            case User::PASSWORD_HASH_MD5_SITE:
1121
                return md5("$login\n$password\n$site");
1122
1123
            case User::PASSWORD_HASH_PLAINTEXT:
1124
                return $password;
1125
1126
            default:
1127
                return md5($password);
1128
        }
1129
    }
1130
}
1131