Completed
Push — EZP-29891 ( 916cf6...0402ff )
by
unknown
16:53
created

LocationService::loadAllLocations()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 46

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 4
nop 2
dl 0
loc 46
rs 9.1781
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * File containing the eZ\Publish\Core\Repository\LocationService 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\PermissionCriterionResolver;
12
use eZ\Publish\API\Repository\Values\Content\LocationUpdateStruct;
13
use eZ\Publish\API\Repository\Values\Content\LocationCreateStruct;
14
use eZ\Publish\API\Repository\Values\Content\ContentInfo;
15
use eZ\Publish\API\Repository\Values\Content\Location as APILocation;
16
use eZ\Publish\API\Repository\Values\Content\LocationList;
17
use eZ\Publish\SPI\Persistence\Content\Location as SPILocation;
18
use eZ\Publish\SPI\Persistence\Content\Location\UpdateStruct;
19
use eZ\Publish\API\Repository\LocationService as LocationServiceInterface;
20
use eZ\Publish\API\Repository\Repository as RepositoryInterface;
21
use eZ\Publish\SPI\Persistence\Handler;
22
use eZ\Publish\API\Repository\Values\Content\Query;
23
use eZ\Publish\API\Repository\Values\Content\LocationQuery;
24
use eZ\Publish\API\Repository\Values\Content\Query\Criterion;
25
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\LogicalAnd as CriterionLogicalAnd;
26
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\LogicalNot as CriterionLogicalNot;
27
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\Subtree as CriterionSubtree;
28
use eZ\Publish\API\Repository\Exceptions\NotFoundException as APINotFoundException;
29
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentValue;
30
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException;
31
use eZ\Publish\Core\Base\Exceptions\BadStateException;
32
use eZ\Publish\Core\Base\Exceptions\UnauthorizedException;
33
use Exception;
34
use Psr\Log\LoggerInterface;
35
use Psr\Log\NullLogger;
36
use eZ\Publish\API\Repository\Values\ContentType\ContentType;
37
38
/**
39
 * Location service, used for complex subtree operations.
40
 *
41
 * @example Examples/location.php
42
 */
43
class LocationService implements LocationServiceInterface
44
{
45
    /**
46
     * @var \eZ\Publish\Core\Repository\Repository
47
     */
48
    protected $repository;
49
50
    /**
51
     * @var \eZ\Publish\SPI\Persistence\Handler
52
     */
53
    protected $persistenceHandler;
54
55
    /**
56
     * @var array
57
     */
58
    protected $settings;
59
60
    /**
61
     * @var \eZ\Publish\Core\Repository\Helper\DomainMapper
62
     */
63
    protected $domainMapper;
64
65
    /**
66
     * @var \eZ\Publish\Core\Repository\Helper\NameSchemaService
67
     */
68
    protected $nameSchemaService;
69
70
    /**
71
     * @var \eZ\Publish\API\Repository\PermissionCriterionResolver
72
     */
73
    protected $permissionCriterionResolver;
74
75
    /**
76
     * @var \Psr\Log\LoggerInterface
77
     */
78
    private $logger;
79
80
    /**
81
     * Setups service with reference to repository object that created it & corresponding handler.
82
     *
83
     * @param \eZ\Publish\API\Repository\Repository $repository
84
     * @param \eZ\Publish\SPI\Persistence\Handler $handler
85
     * @param \eZ\Publish\Core\Repository\Helper\DomainMapper $domainMapper
86
     * @param \eZ\Publish\Core\Repository\Helper\NameSchemaService $nameSchemaService
87
     * @param \eZ\Publish\API\Repository\PermissionCriterionResolver $permissionCriterionResolver
88
     * @param array $settings
89
     * @param \Psr\Log\LoggerInterface|null $logger
90
     */
91
    public function __construct(
92
        RepositoryInterface $repository,
93
        Handler $handler,
94
        Helper\DomainMapper $domainMapper,
95
        Helper\NameSchemaService $nameSchemaService,
96
        PermissionCriterionResolver $permissionCriterionResolver,
97
        array $settings = array(),
98
        LoggerInterface $logger = null
99
    ) {
100
        $this->repository = $repository;
0 ignored issues
show
Documentation Bug introduced by
$repository is of type object<eZ\Publish\API\Repository\Repository>, but the property $repository was declared to be of type object<eZ\Publish\Core\Repository\Repository>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
101
        $this->persistenceHandler = $handler;
102
        $this->domainMapper = $domainMapper;
103
        $this->nameSchemaService = $nameSchemaService;
104
        // Union makes sure default settings are ignored if provided in argument
105
        $this->settings = $settings + array(
106
            //'defaultSetting' => array(),
107
        );
108
        $this->permissionCriterionResolver = $permissionCriterionResolver;
109
        $this->logger = null !== $logger ? $logger : new NullLogger();
110
    }
111
112
    /**
113
     * Copies the subtree starting from $subtree as a new subtree of $targetLocation.
114
     *
115
     * Only the items on which the user has read access are copied.
116
     *
117
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed copy the subtree to the given parent location
118
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user does not have read access to the whole source subtree
119
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the target location is a sub location of the given location
120
     *
121
     * @param \eZ\Publish\API\Repository\Values\Content\Location $subtree - the subtree denoted by the location to copy
122
     * @param \eZ\Publish\API\Repository\Values\Content\Location $targetParentLocation - the target parent location for the copy operation
123
     *
124
     * @return \eZ\Publish\API\Repository\Values\Content\Location The newly created location of the copied subtree
125
     */
126
    public function copySubtree(APILocation $subtree, APILocation $targetParentLocation)
127
    {
128
        $loadedSubtree = $this->loadLocation($subtree->id);
129
        $loadedTargetLocation = $this->loadLocation($targetParentLocation->id);
130
131
        if (stripos($loadedTargetLocation->pathString, $loadedSubtree->pathString) !== false) {
132
            throw new InvalidArgumentException('targetParentLocation', 'target parent location is a sub location of the given subtree');
133
        }
134
135
        // check create permission on target
136
        if (!$this->repository->canUser('content', 'create', $loadedSubtree->getContentInfo(), $loadedTargetLocation)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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...
137
            throw new UnauthorizedException('content', 'create', ['locationId' => $loadedTargetLocation->id]);
138
        }
139
140
        /** Check read access to whole source subtree
141
         * @var bool|\eZ\Publish\API\Repository\Values\Content\Query\Criterion
142
         */
143
        $contentReadCriterion = $this->permissionCriterionResolver->getPermissionsCriterion();
0 ignored issues
show
Bug introduced by
The call to getPermissionsCriterion() misses some required arguments starting with $module.
Loading history...
144 View Code Duplication
        if ($contentReadCriterion === false) {
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...
145
            throw new UnauthorizedException('content', 'read');
146
        } elseif ($contentReadCriterion !== true) {
147
            // Query if there are any content in subtree current user don't have access to
148
            $query = new Query(
149
                array(
150
                    'limit' => 0,
151
                    'filter' => new CriterionLogicalAnd(
152
                        array(
153
                            new CriterionSubtree($loadedSubtree->pathString),
154
                            new CriterionLogicalNot($contentReadCriterion),
0 ignored issues
show
Bug introduced by
It seems like $contentReadCriterion defined by $this->permissionCriteri...tPermissionsCriterion() on line 143 can also be of type boolean; however, eZ\Publish\API\Repositor...gicalNot::__construct() does only seem to accept object<eZ\Publish\API\Re...ontent\Query\Criterion>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
155
                        )
156
                    ),
157
                )
158
            );
159
            $result = $this->repository->getSearchService()->findContent($query, array(), false);
160
            if ($result->totalCount > 0) {
161
                throw new UnauthorizedException('content', 'read');
162
            }
163
        }
164
165
        $this->repository->beginTransaction();
166
        try {
167
            $newLocation = $this->persistenceHandler->locationHandler()->copySubtree(
168
                $loadedSubtree->id,
169
                $loadedTargetLocation->id,
170
                $this->repository->getPermissionResolver()->getCurrentUserReference()->getUserId()
171
            );
172
173
            $content = $this->repository->getContentService()->loadContent($newLocation->contentId);
174
            $urlAliasNames = $this->nameSchemaService->resolveUrlAliasSchema($content);
175 View Code Duplication
            foreach ($urlAliasNames as $languageCode => $name) {
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...
176
                $this->persistenceHandler->urlAliasHandler()->publishUrlAliasForLocation(
177
                    $newLocation->id,
178
                    $loadedTargetLocation->id,
179
                    $name,
180
                    $languageCode,
181
                    $content->contentInfo->alwaysAvailable
182
                );
183
            }
184
185
            $this->persistenceHandler->urlAliasHandler()->locationCopied(
186
                $loadedSubtree->id,
187
                $newLocation->id,
188
                $loadedTargetLocation->id
189
            );
190
191
            $this->repository->commit();
192
        } catch (Exception $e) {
193
            $this->repository->rollback();
194
            throw $e;
195
        }
196
197
        return $this->domainMapper->buildLocationDomainObject($newLocation);
198
    }
199
200
    /**
201
     * Loads a location object from its $locationId.
202
     *
203
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed to read this location
204
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException If the specified location is not found
205
     *
206
     * @param mixed $locationId
207
     *
208
     * @return \eZ\Publish\API\Repository\Values\Content\Location
209
     */
210
    public function loadLocation($locationId)
211
    {
212
        $spiLocation = $this->persistenceHandler->locationHandler()->load($locationId);
213
        $location = $this->domainMapper->buildLocationDomainObject($spiLocation);
214
        if (!$this->repository->canUser('content', 'read', $location->getContentInfo(), $location)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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...
215
            throw new UnauthorizedException('content', 'read', ['locationId' => $location->id]);
216
        }
217
218
        return $location;
219
    }
220
221
    /**
222
     * Loads a location object from its $remoteId.
223
     *
224
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed to read this location
225
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException If more than one location with same remote ID was found
226
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException If the specified location is not found
227
     *
228
     * @param string $remoteId
229
     *
230
     * @return \eZ\Publish\API\Repository\Values\Content\Location
231
     */
232
    public function loadLocationByRemoteId($remoteId)
233
    {
234
        if (!is_string($remoteId)) {
235
            throw new InvalidArgumentValue('remoteId', $remoteId);
236
        }
237
238
        $spiLocation = $this->persistenceHandler->locationHandler()->loadByRemoteId($remoteId);
239
        $location = $this->domainMapper->buildLocationDomainObject($spiLocation);
240
        if (!$this->repository->canUser('content', 'read', $location->getContentInfo(), $location)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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...
241
            throw new UnauthorizedException('content', 'read', ['locationId' => $location->id]);
242
        }
243
244
        return $location;
245
    }
246
247
    /**
248
     * {@inheritdoc}
249
     */
250
    public function loadLocations(ContentInfo $contentInfo, APILocation $rootLocation = null)
251
    {
252
        if (!$contentInfo->published) {
253
            throw new BadStateException('$contentInfo', 'ContentInfo has no published versions');
254
        }
255
256
        $spiLocations = $this->persistenceHandler->locationHandler()->loadLocationsByContent(
257
            $contentInfo->id,
258
            $rootLocation !== null ? $rootLocation->id : null
259
        );
260
261
        $locations = [];
262
        foreach ($spiLocations as $spiLocation) {
263
            $location = $this->domainMapper->buildLocationDomainObject($spiLocation);
264
            if ($this->repository->canUser('content', 'read', $location->getContentInfo(), $location)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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...
265
                $locations[] = $location;
266
            }
267
        }
268
269
        return $locations;
270
    }
271
272
    /**
273
     * Loads children which are readable by the current user of a location object sorted by sortField and sortOrder.
274
     *
275
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
276
     * @param int $offset the start offset for paging
277
     * @param int $limit the number of locations returned
278
     *
279
     * @return \eZ\Publish\API\Repository\Values\Content\LocationList
280
     */
281
    public function loadLocationChildren(APILocation $location, $offset = 0, $limit = 25)
282
    {
283
        if (!$this->domainMapper->isValidLocationSortField($location->sortField)) {
284
            throw new InvalidArgumentValue('sortField', $location->sortField, 'Location');
285
        }
286
287
        if (!$this->domainMapper->isValidLocationSortOrder($location->sortOrder)) {
288
            throw new InvalidArgumentValue('sortOrder', $location->sortOrder, 'Location');
289
        }
290
291
        if (!is_int($offset)) {
292
            throw new InvalidArgumentValue('offset', $offset);
293
        }
294
295
        if (!is_int($limit)) {
296
            throw new InvalidArgumentValue('limit', $limit);
297
        }
298
299
        $childLocations = array();
300
        $searchResult = $this->searchChildrenLocations($location, $offset, $limit);
301
        foreach ($searchResult->searchHits as $searchHit) {
302
            $childLocations[] = $searchHit->valueObject;
303
        }
304
305
        return new LocationList(
306
            array(
307
                'locations' => $childLocations,
308
                'totalCount' => $searchResult->totalCount,
309
            )
310
        );
311
    }
312
313
    /**
314
     * Returns the number of children which are readable by the current user of a location object.
315
     *
316
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
317
     *
318
     * @return int
319
     */
320
    public function getLocationChildCount(APILocation $location)
321
    {
322
        $searchResult = $this->searchChildrenLocations($location, 0, 0);
323
324
        return $searchResult->totalCount;
325
    }
326
327
    /**
328
     * Searches children locations of the provided parent location id.
329
     *
330
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
331
     * @param int $offset
332
     * @param int $limit
333
     *
334
     * @return \eZ\Publish\API\Repository\Values\Content\Search\SearchResult
335
     */
336
    protected function searchChildrenLocations(APILocation $location, $offset = 0, $limit = -1)
337
    {
338
        $query = new LocationQuery([
339
            'filter' => new Criterion\ParentLocationId($location->id),
340
            'offset' => $offset >= 0 ? (int)$offset : 0,
341
            'limit' => $limit >= 0 ? (int)$limit : null,
342
            'sortClauses' => $location->getSortClauses(),
343
        ]);
344
345
        return $this->repository->getSearchService()->findLocations($query);
346
    }
347
348
    /**
349
     * Creates the new $location in the content repository for the given content.
350
     *
351
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed to create this location
352
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the content is already below the specified parent
353
     *                                        or the parent is a sub location of the location of the content
354
     *                                        or if set the remoteId exists already
355
     *
356
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
357
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct $locationCreateStruct
358
     *
359
     * @return \eZ\Publish\API\Repository\Values\Content\Location the newly created Location
360
     */
361
    public function createLocation(ContentInfo $contentInfo, LocationCreateStruct $locationCreateStruct)
362
    {
363
        $content = $this->repository->getContentService()->loadContent($contentInfo->id);
364
        $parentLocation = $this->loadLocation($locationCreateStruct->parentLocationId);
365
366
        if (!$this->repository->canUser('content', 'manage_locations', $content->contentInfo)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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...
367
            throw new UnauthorizedException('content', 'manage_locations', ['contentId' => $contentInfo->id]);
368
        }
369
370
        if (!$this->repository->canUser('content', 'create', $content->contentInfo, $parentLocation)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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...
371
            throw new UnauthorizedException('content', 'create', ['locationId' => $parentLocation->id]);
372
        }
373
374
        // Check if the parent is a sub location of one of the existing content locations (this also solves the
375
        // situation where parent location actually one of the content locations),
376
        // or if the content already has location below given location create struct parent
377
        $existingContentLocations = $this->loadLocations($content->contentInfo);
378
        if (!empty($existingContentLocations)) {
379
            foreach ($existingContentLocations as $existingContentLocation) {
380
                if (stripos($parentLocation->pathString, $existingContentLocation->pathString) !== false) {
381
                    throw new InvalidArgumentException(
382
                        '$locationCreateStruct',
383
                        'Specified parent is a sub location of one of the existing content locations.'
384
                    );
385
                }
386
                if ($parentLocation->id == $existingContentLocation->parentLocationId) {
387
                    throw new InvalidArgumentException(
388
                        '$locationCreateStruct',
389
                        'Content is already below the specified parent.'
390
                    );
391
                }
392
            }
393
        }
394
395
        $spiLocationCreateStruct = $this->domainMapper->buildSPILocationCreateStruct(
396
            $locationCreateStruct,
397
            $parentLocation,
398
            $content->contentInfo->mainLocationId !== null ? $content->contentInfo->mainLocationId : true,
399
            $content->contentInfo->id,
400
            $content->contentInfo->currentVersionNo
401
        );
402
403
        $this->repository->beginTransaction();
404
        try {
405
            $newLocation = $this->persistenceHandler->locationHandler()->create($spiLocationCreateStruct);
406
407
            $urlAliasNames = $this->nameSchemaService->resolveUrlAliasSchema($content);
408 View Code Duplication
            foreach ($urlAliasNames as $languageCode => $name) {
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...
409
                $this->persistenceHandler->urlAliasHandler()->publishUrlAliasForLocation(
410
                    $newLocation->id,
411
                    $newLocation->parentId,
412
                    $name,
413
                    $languageCode,
414
                    $content->contentInfo->alwaysAvailable,
415
                    // @todo: this is legacy storage specific for updating ezcontentobject_tree.path_identification_string, to be removed
416
                    $languageCode === $content->contentInfo->mainLanguageCode
417
                );
418
            }
419
420
            $this->repository->commit();
421
        } catch (Exception $e) {
422
            $this->repository->rollback();
423
            throw $e;
424
        }
425
426
        return $this->domainMapper->buildLocationDomainObject($newLocation);
427
    }
428
429
    /**
430
     * Updates $location in the content repository.
431
     *
432
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed to update this location
433
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException   if if set the remoteId exists already
434
     *
435
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
436
     * @param \eZ\Publish\API\Repository\Values\Content\LocationUpdateStruct $locationUpdateStruct
437
     *
438
     * @return \eZ\Publish\API\Repository\Values\Content\Location the updated Location
439
     */
440
    public function updateLocation(APILocation $location, LocationUpdateStruct $locationUpdateStruct)
441
    {
442
        if ($locationUpdateStruct->priority !== null && !is_int($locationUpdateStruct->priority)) {
443
            throw new InvalidArgumentValue('priority', $locationUpdateStruct->priority, 'LocationUpdateStruct');
444
        }
445
446 View Code Duplication
        if ($locationUpdateStruct->remoteId !== null && (!is_string($locationUpdateStruct->remoteId) || empty($locationUpdateStruct->remoteId))) {
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...
447
            throw new InvalidArgumentValue('remoteId', $locationUpdateStruct->remoteId, 'LocationUpdateStruct');
448
        }
449
450
        if ($locationUpdateStruct->sortField !== null && !$this->domainMapper->isValidLocationSortField($locationUpdateStruct->sortField)) {
451
            throw new InvalidArgumentValue('sortField', $locationUpdateStruct->sortField, 'LocationUpdateStruct');
452
        }
453
454
        if ($locationUpdateStruct->sortOrder !== null && !$this->domainMapper->isValidLocationSortOrder($locationUpdateStruct->sortOrder)) {
455
            throw new InvalidArgumentValue('sortOrder', $locationUpdateStruct->sortOrder, 'LocationUpdateStruct');
456
        }
457
458
        $loadedLocation = $this->loadLocation($location->id);
459
460
        if ($locationUpdateStruct->remoteId !== null) {
461
            try {
462
                $existingLocation = $this->loadLocationByRemoteId($locationUpdateStruct->remoteId);
463
                if ($existingLocation !== null && $existingLocation->id !== $loadedLocation->id) {
464
                    throw new InvalidArgumentException('locationUpdateStruct', 'location with provided remote ID already exists');
465
                }
466
            } catch (APINotFoundException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
467
            }
468
        }
469
470
        if (!$this->repository->canUser('content', 'edit', $loadedLocation->getContentInfo(), $loadedLocation)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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...
471
            throw new UnauthorizedException('content', 'edit', ['locationId' => $loadedLocation->id]);
472
        }
473
474
        $updateStruct = new UpdateStruct();
475
        $updateStruct->priority = $locationUpdateStruct->priority !== null ? $locationUpdateStruct->priority : $loadedLocation->priority;
476
        $updateStruct->remoteId = $locationUpdateStruct->remoteId !== null ? trim($locationUpdateStruct->remoteId) : $loadedLocation->remoteId;
477
        $updateStruct->sortField = $locationUpdateStruct->sortField !== null ? $locationUpdateStruct->sortField : $loadedLocation->sortField;
478
        $updateStruct->sortOrder = $locationUpdateStruct->sortOrder !== null ? $locationUpdateStruct->sortOrder : $loadedLocation->sortOrder;
479
480
        $this->repository->beginTransaction();
481
        try {
482
            $this->persistenceHandler->locationHandler()->update($updateStruct, $loadedLocation->id);
483
            $this->repository->commit();
484
        } catch (Exception $e) {
485
            $this->repository->rollback();
486
            throw $e;
487
        }
488
489
        return $this->loadLocation($loadedLocation->id);
490
    }
491
492
    /**
493
     * Swaps the contents held by $location1 and $location2.
494
     *
495
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed to swap content
496
     *
497
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location1
498
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location2
499
     */
500
    public function swapLocation(APILocation $location1, APILocation $location2)
501
    {
502
        $loadedLocation1 = $this->loadLocation($location1->id);
503
        $loadedLocation2 = $this->loadLocation($location2->id);
504
505
        if (!$this->repository->canUser('content', 'edit', $loadedLocation1->getContentInfo(), $loadedLocation1)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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...
506
            throw new UnauthorizedException('content', 'edit', ['locationId' => $loadedLocation1->id]);
507
        }
508
        if (!$this->repository->canUser('content', 'edit', $loadedLocation2->getContentInfo(), $loadedLocation2)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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...
509
            throw new UnauthorizedException('content', 'edit', ['locationId' => $loadedLocation2->id]);
510
        }
511
512
        $this->repository->beginTransaction();
513
        try {
514
            $this->persistenceHandler->locationHandler()->swap($loadedLocation1->id, $loadedLocation2->id);
515
            $this->persistenceHandler->urlAliasHandler()->locationSwapped(
516
                $location1->id,
517
                $location1->parentLocationId,
518
                $location2->id,
519
                $location2->parentLocationId
520
            );
521
            $this->repository->commit();
522
        } catch (Exception $e) {
523
            $this->repository->rollback();
524
            throw $e;
525
        }
526
    }
527
528
    /**
529
     * Hides the $location and marks invisible all descendants of $location.
530
     *
531
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed to hide this location
532
     *
533
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
534
     *
535
     * @return \eZ\Publish\API\Repository\Values\Content\Location $location, with updated hidden value
536
     */
537 View Code Duplication
    public function hideLocation(APILocation $location)
538
    {
539
        if (!$this->repository->canUser('content', 'hide', $location->getContentInfo(), $location)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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...
540
            throw new UnauthorizedException('content', 'hide', ['locationId' => $location->id]);
541
        }
542
543
        $this->repository->beginTransaction();
544
        try {
545
            $this->persistenceHandler->locationHandler()->hide($location->id);
546
            $this->repository->commit();
547
        } catch (Exception $e) {
548
            $this->repository->rollback();
549
            throw $e;
550
        }
551
552
        return $this->loadLocation($location->id);
553
    }
554
555
    /**
556
     * Unhides the $location.
557
     *
558
     * This method and marks visible all descendants of $locations
559
     * until a hidden location is found.
560
     *
561
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed to unhide this location
562
     *
563
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
564
     *
565
     * @return \eZ\Publish\API\Repository\Values\Content\Location $location, with updated hidden value
566
     */
567 View Code Duplication
    public function unhideLocation(APILocation $location)
568
    {
569
        if (!$this->repository->canUser('content', 'hide', $location->getContentInfo(), $location)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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...
570
            throw new UnauthorizedException('content', 'hide', ['locationId' => $location->id]);
571
        }
572
573
        $this->repository->beginTransaction();
574
        try {
575
            $this->persistenceHandler->locationHandler()->unHide($location->id);
576
            $this->repository->commit();
577
        } catch (Exception $e) {
578
            $this->repository->rollback();
579
            throw $e;
580
        }
581
582
        return $this->loadLocation($location->id);
583
    }
584
585
    /**
586
     * Moves the subtree to $newParentLocation.
587
     *
588
     * If a user has the permission to move the location to a target location
589
     * he can do it regardless of an existing descendant on which the user has no permission.
590
     *
591
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed to move this location to the target
592
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user does not have read access to the whole source subtree
593
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException If the new parent is in a subtree of the location
594
     *
595
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
596
     * @param \eZ\Publish\API\Repository\Values\Content\Location $newParentLocation
597
     */
598
    public function moveSubtree(APILocation $location, APILocation $newParentLocation)
599
    {
600
        $location = $this->loadLocation($location->id);
601
        $newParentLocation = $this->loadLocation($newParentLocation->id);
602
603
        // check create permission on target location
604
        if (!$this->repository->canUser('content', 'create', $location->getContentInfo(), $newParentLocation)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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...
605
            throw new UnauthorizedException('content', 'create', ['locationId' => $newParentLocation->id]);
606
        }
607
608
        /** Check read access to whole source subtree
609
         * @var bool|\eZ\Publish\API\Repository\Values\Content\Query\Criterion
610
         */
611
        $contentReadCriterion = $this->permissionCriterionResolver->getPermissionsCriterion();
0 ignored issues
show
Bug introduced by
The call to getPermissionsCriterion() misses some required arguments starting with $module.
Loading history...
612 View Code Duplication
        if ($contentReadCriterion === false) {
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...
613
            throw new UnauthorizedException('content', 'read');
614
        } elseif ($contentReadCriterion !== true) {
615
            // Query if there are any content in subtree current user don't have access to
616
            $query = new Query(
617
                array(
618
                    'limit' => 0,
619
                    'filter' => new CriterionLogicalAnd(
620
                        array(
621
                            new CriterionSubtree($location->pathString),
622
                            new CriterionLogicalNot($contentReadCriterion),
0 ignored issues
show
Bug introduced by
It seems like $contentReadCriterion defined by $this->permissionCriteri...tPermissionsCriterion() on line 611 can also be of type boolean; however, eZ\Publish\API\Repositor...gicalNot::__construct() does only seem to accept object<eZ\Publish\API\Re...ontent\Query\Criterion>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
623
                        )
624
                    ),
625
                )
626
            );
627
            $result = $this->repository->getSearchService()->findContent($query, array(), false);
628
            if ($result->totalCount > 0) {
629
                throw new UnauthorizedException('content', 'read');
630
            }
631
        }
632
633
        if (strpos($newParentLocation->pathString, $location->pathString) === 0) {
634
            throw new InvalidArgumentException(
635
                '$newParentLocation',
636
                'new parent location is in a subtree of the given $location'
637
            );
638
        }
639
640
        $this->repository->beginTransaction();
641
        try {
642
            $this->persistenceHandler->locationHandler()->move($location->id, $newParentLocation->id);
643
644
            $content = $this->repository->getContentService()->loadContent($location->contentId);
645
            $urlAliasNames = $this->nameSchemaService->resolveUrlAliasSchema($content);
646 View Code Duplication
            foreach ($urlAliasNames as $languageCode => $name) {
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...
647
                $this->persistenceHandler->urlAliasHandler()->publishUrlAliasForLocation(
648
                    $location->id,
649
                    $newParentLocation->id,
650
                    $name,
651
                    $languageCode,
652
                    $content->contentInfo->alwaysAvailable
653
                );
654
            }
655
656
            $this->persistenceHandler->urlAliasHandler()->locationMoved(
657
                $location->id,
658
                $location->parentLocationId,
659
                $newParentLocation->id
660
            );
661
662
            $this->repository->commit();
663
        } catch (Exception $e) {
664
            $this->repository->rollback();
665
            throw $e;
666
        }
667
    }
668
669
    /**
670
     * Deletes $location and all its descendants.
671
     *
672
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user is not allowed to delete this location or a descendant
673
     *
674
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
675
     */
676
    public function deleteLocation(APILocation $location)
677
    {
678
        $location = $this->loadLocation($location->id);
679
680
        if (!$this->repository->canUser('content', 'manage_locations', $location->getContentInfo())) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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...
681
            throw new UnauthorizedException('content', 'manage_locations', ['locationId' => $location->id]);
682
        }
683
        if (!$this->repository->canUser('content', 'remove', $location->getContentInfo(), $location)) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::canUser() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::canUser() instead. Check if user has access to a given action on a given value object. Indicates if the current user is allowed to perform an action given by the function on the given
objects.

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...
684
            throw new UnauthorizedException('content', 'remove', ['locationId' => $location->id]);
685
        }
686
687
        /** Check remove access to descendants
688
         * @var bool|\eZ\Publish\API\Repository\Values\Content\Query\Criterion
689
         */
690
        $contentReadCriterion = $this->permissionCriterionResolver->getPermissionsCriterion('content', 'remove');
691 View Code Duplication
        if ($contentReadCriterion === false) {
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...
692
            throw new UnauthorizedException('content', 'remove');
693
        } elseif ($contentReadCriterion !== true) {
694
            // Query if there are any content in subtree current user don't have access to
695
            $query = new Query(
696
                array(
697
                    'limit' => 0,
698
                    'filter' => new CriterionLogicalAnd(
699
                        array(
700
                            new CriterionSubtree($location->pathString),
701
                            new CriterionLogicalNot($contentReadCriterion),
0 ignored issues
show
Bug introduced by
It seems like $contentReadCriterion defined by $this->permissionCriteri...on('content', 'remove') on line 690 can also be of type boolean; however, eZ\Publish\API\Repositor...gicalNot::__construct() does only seem to accept object<eZ\Publish\API\Re...ontent\Query\Criterion>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
702
                        )
703
                    ),
704
                )
705
            );
706
            $result = $this->repository->getSearchService()->findContent($query, array(), false);
707
            if ($result->totalCount > 0) {
708
                throw new UnauthorizedException('content', 'remove');
709
            }
710
        }
711
712
        $this->repository->beginTransaction();
713
        try {
714
            $this->persistenceHandler->locationHandler()->removeSubtree($location->id);
715
            $this->persistenceHandler->urlAliasHandler()->locationDeleted($location->id);
716
            $this->repository->commit();
717
        } catch (Exception $e) {
718
            $this->repository->rollback();
719
            throw $e;
720
        }
721
    }
722
723
    /**
724
     * Instantiates a new location create class.
725
     *
726
     * @param mixed $parentLocationId the parent under which the new location should be created
727
     * @param eZ\Publish\API\Repository\Values\ContentType\ContentType|null $contentType
728
     *
729
     * @return \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct
730
     */
731 View Code Duplication
    public function newLocationCreateStruct($parentLocationId, ContentType $contentType = null)
732
    {
733
        $properties = [
734
            'parentLocationId' => $parentLocationId,
735
        ];
736
        if ($contentType) {
737
            $properties['sortField'] = $contentType->defaultSortField;
738
            $properties['sortOrder'] = $contentType->defaultSortOrder;
739
        }
740
741
        return new LocationCreateStruct($properties);
742
    }
743
744
    /**
745
     * Instantiates a new location update class.
746
     *
747
     * @return \eZ\Publish\API\Repository\Values\Content\LocationUpdateStruct
748
     */
749
    public function newLocationUpdateStruct()
750
    {
751
        return new LocationUpdateStruct();
752
    }
753
754
    /**
755
     * Get the total number of all existing Locations. Can be combined with loadAllLocations.
756
     *
757
     * @see loadAllLocations
758
     *
759
     * @return int Total number of Locations
760
     */
761
    public function getAllLocationsCount()
762
    {
763
        return $this->persistenceHandler->locationHandler()->countAllLocations();
764
    }
765
766
    /**
767
     * Bulk-load all existing Locations, constrained by $limit and $offset to paginate results.
768
     *
769
     * @param int $offset
770
     * @param int $limit
771
     *
772
     * @return \eZ\Publish\API\Repository\Values\Content\Location[]
773
     *
774
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
775
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
776
     */
777
    public function loadAllLocations($offset = 0, $limit = 25)
778
    {
779
        $spiLocations = $this->persistenceHandler->locationHandler()->loadAllLocations(
780
            $offset,
781
            $limit
782
        );
783
        $contentIds = array_unique(
784
            array_map(
785
                function (SPILocation $spiLocation) {
786
                    return $spiLocation->contentId;
787
                },
788
                $spiLocations
789
            )
790
        );
791
792
        $permissionResolver = $this->repository->getPermissionResolver();
793
        $spiContentInfoList = $this->persistenceHandler->contentHandler()->loadContentInfoList(
794
            $contentIds
795
        );
796
        $locations = [];
797
        foreach ($spiLocations as $spiLocation) {
798
            if (!isset($spiContentInfoList[$spiLocation->contentId])) {
799
                $this->logger->warning(
800
                    sprintf(
801
                        'Location %d has missing Content %d',
802
                        $spiLocation->id,
803
                        $spiLocation->contentId
804
                    )
805
                );
806
                continue;
807
            }
808
809
            $location = $this->domainMapper->buildLocationDomainObject(
810
                $spiLocation,
811
                $spiContentInfoList[$spiLocation->contentId]
812
            );
813
814
            $contentInfo = $location->getContentInfo();
815
            if (!$permissionResolver->canUser('content', 'read', $contentInfo, [$location])) {
816
                continue;
817
            }
818
            $locations[] = $location;
819
        }
820
821
        return $locations;
822
    }
823
}
824