Completed
Push — bcrypt ( 3e60ca )
by André
32:03
created

UserService::verifyPassword()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

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

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

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

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

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

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

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
200
                )
201
            );
202
        }
203
204
        return $subUserGroups;
205
    }
206
207
    /**
208
     * Returns (searches) subgroups of a user group described by its main location.
209
     *
210
     * @param mixed $locationId
211
     * @param int|null $sortField
212
     * @param int $sortOrder
213
     * @param int $offset
214
     * @param int $limit
215
     *
216
     * @return \eZ\Publish\API\Repository\Values\Content\Search\SearchResult
217
     */
218
    protected function searchSubGroups($locationId, $sortField = null, $sortOrder = Location::SORT_ORDER_ASC, $offset = 0, $limit = 25)
219
    {
220
        $searchQuery = new LocationQuery();
221
222
        $searchQuery->offset = $offset;
223
        $searchQuery->limit = $limit;
224
225
        $searchQuery->filter = new CriterionLogicalAnd(
226
            array(
227
                new CriterionContentTypeId($this->settings['userGroupClassID']),
228
                new CriterionParentLocationId($locationId),
229
            )
230
        );
231
232
        $searchQuery->sortClauses = array();
233
        if ($sortField !== null) {
234
            $searchQuery->sortClauses[] = $this->getSortClauseBySortField($sortField, $sortOrder);
235
        }
236
237
        return $this->repository->getSearchService()->findLocations($searchQuery, array(), false);
238
    }
239
240
    /**
241
     * Removes a user group.
242
     *
243
     * the users which are not assigned to other groups will be deleted.
244
     *
245
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $userGroup
246
     *
247
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to create a user group
248
     */
249 View Code Duplication
    public function deleteUserGroup(APIUserGroup $userGroup)
250
    {
251
        $loadedUserGroup = $this->loadUserGroup($userGroup->id);
252
253
        $this->repository->beginTransaction();
254
        try {
255
            //@todo: what happens to sub user groups and users below sub user groups
256
            $this->repository->getContentService()->deleteContent($loadedUserGroup->getVersionInfo()->getContentInfo());
257
            $this->repository->commit();
258
        } catch (Exception $e) {
259
            $this->repository->rollback();
260
            throw $e;
261
        }
262
    }
263
264
    /**
265
     * Moves the user group to another parent.
266
     *
267
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $userGroup
268
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $newParent
269
     *
270
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to move the user group
271
     */
272
    public function moveUserGroup(APIUserGroup $userGroup, APIUserGroup $newParent)
273
    {
274
        $loadedUserGroup = $this->loadUserGroup($userGroup->id);
275
        $loadedNewParent = $this->loadUserGroup($newParent->id);
276
277
        $locationService = $this->repository->getLocationService();
278
279
        if ($loadedUserGroup->getVersionInfo()->getContentInfo()->mainLocationId === null) {
280
            throw new BadStateException('userGroup', 'existing user group is not stored and/or does not have any location yet');
281
        }
282
283
        if ($loadedNewParent->getVersionInfo()->getContentInfo()->mainLocationId === null) {
284
            throw new BadStateException('newParent', 'new user group is not stored and/or does not have any location yet');
285
        }
286
287
        $userGroupMainLocation = $locationService->loadLocation(
288
            $loadedUserGroup->getVersionInfo()->getContentInfo()->mainLocationId
289
        );
290
        $newParentMainLocation = $locationService->loadLocation(
291
            $loadedNewParent->getVersionInfo()->getContentInfo()->mainLocationId
292
        );
293
294
        $this->repository->beginTransaction();
295
        try {
296
            $locationService->moveSubtree($userGroupMainLocation, $newParentMainLocation);
297
            $this->repository->commit();
298
        } catch (Exception $e) {
299
            $this->repository->rollback();
300
            throw $e;
301
        }
302
    }
303
304
    /**
305
     * Updates the group profile with fields and meta data.
306
     *
307
     * 4.x: If the versionUpdateStruct is set in $userGroupUpdateStruct, this method internally creates a content draft, updates ts with the provided data
308
     * and publishes the draft. If a draft is explicitly required, the user group can be updated via the content service methods.
309
     *
310
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $userGroup
311
     * @param \eZ\Publish\API\Repository\Values\User\UserGroupUpdateStruct $userGroupUpdateStruct
312
     *
313
     * @return \eZ\Publish\API\Repository\Values\User\UserGroup
314
     *
315
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to update the user group
316
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $userGroupUpdateStruct is not valid
317
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException if a required field is set empty
318
     */
319
    public function updateUserGroup(APIUserGroup $userGroup, UserGroupUpdateStruct $userGroupUpdateStruct)
320
    {
321
        if ($userGroupUpdateStruct->contentUpdateStruct === null &&
322
            $userGroupUpdateStruct->contentMetadataUpdateStruct === null) {
323
            // both update structs are empty, nothing to do
324
            return $userGroup;
325
        }
326
327
        $contentService = $this->repository->getContentService();
328
329
        $loadedUserGroup = $this->loadUserGroup($userGroup->id);
330
331
        $this->repository->beginTransaction();
332
        try {
333
            $publishedContent = $loadedUserGroup;
334 View Code Duplication
            if ($userGroupUpdateStruct->contentUpdateStruct !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
335
                $contentDraft = $contentService->createContentDraft($loadedUserGroup->getVersionInfo()->getContentInfo());
336
337
                $contentDraft = $contentService->updateContent(
338
                    $contentDraft->getVersionInfo(),
339
                    $userGroupUpdateStruct->contentUpdateStruct
340
                );
341
342
                $publishedContent = $contentService->publishVersion($contentDraft->getVersionInfo());
343
            }
344
345
            if ($userGroupUpdateStruct->contentMetadataUpdateStruct !== null) {
346
                $publishedContent = $contentService->updateContentMetadata(
347
                    $publishedContent->getVersionInfo()->getContentInfo(),
348
                    $userGroupUpdateStruct->contentMetadataUpdateStruct
349
                );
350
            }
351
352
            $this->repository->commit();
353
        } catch (Exception $e) {
354
            $this->repository->rollback();
355
            throw $e;
356
        }
357
358
        return $this->buildDomainUserGroupObject($publishedContent);
359
    }
360
361
    /**
362
     * Create a new user. The created user is published by this method.
363
     *
364
     * @param \eZ\Publish\API\Repository\Values\User\UserCreateStruct $userCreateStruct the data used for creating the user
365
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup[] $parentGroups the groups which are assigned to the user after creation
366
     *
367
     * @return \eZ\Publish\API\Repository\Values\User\User
368
     *
369
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to move the user group
370
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException if a field in the $userCreateStruct is not valid
371
     * @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException if a required field is missing or set to an empty value
372
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if a user with provided login already exists
373
     */
374
    public function createUser(APIUserCreateStruct $userCreateStruct, array $parentGroups)
375
    {
376
        if (empty($parentGroups)) {
377
            throw new InvalidArgumentValue('parentGroups', $parentGroups);
378
        }
379
380
        if (!is_string($userCreateStruct->login) || empty($userCreateStruct->login)) {
381
            throw new InvalidArgumentValue('login', $userCreateStruct->login, 'UserCreateStruct');
382
        }
383
384 View Code Duplication
        if (!is_string($userCreateStruct->email) || empty($userCreateStruct->email)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
385
            throw new InvalidArgumentValue('email', $userCreateStruct->email, 'UserCreateStruct');
386
        }
387
388
        if (!ezcMailTools::validateEmailAddress($userCreateStruct->email)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression \ezcMailTools::validateE...serCreateStruct->email) of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

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

$a = canBeFalseAndNull();

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

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

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

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

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

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

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

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

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

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

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

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

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

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

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