Completed
Push — master ( 0b3034...a98e2b )
by
unknown
36:06 queued 14:31
created

LocationService::swapLocation()   A

Complexity

Conditions 4
Paths 7

Size

Total Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 7
nop 2
dl 0
loc 28
rs 9.472
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\Language;
13
use eZ\Publish\API\Repository\Values\Content\LocationUpdateStruct;
14
use eZ\Publish\API\Repository\Values\Content\LocationCreateStruct;
15
use eZ\Publish\API\Repository\Values\Content\ContentInfo;
16
use eZ\Publish\API\Repository\Values\Content\Location as APILocation;
17
use eZ\Publish\API\Repository\Values\Content\LocationList;
18
use eZ\Publish\API\Repository\Values\Content\VersionInfo;
19
use eZ\Publish\SPI\Persistence\Content\Location as SPILocation;
20
use eZ\Publish\SPI\Persistence\Content\Location\UpdateStruct;
21
use eZ\Publish\API\Repository\LocationService as LocationServiceInterface;
22
use eZ\Publish\API\Repository\Repository as RepositoryInterface;
23
use eZ\Publish\SPI\Persistence\Handler;
24
use eZ\Publish\API\Repository\Values\Content\Query;
25
use eZ\Publish\API\Repository\Values\Content\LocationQuery;
26
use eZ\Publish\API\Repository\Values\Content\Query\Criterion;
27
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\LogicalAnd as CriterionLogicalAnd;
28
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\LogicalNot as CriterionLogicalNot;
29
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\Subtree as CriterionSubtree;
30
use eZ\Publish\API\Repository\Exceptions\NotFoundException as APINotFoundException;
31
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentValue;
32
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException;
33
use eZ\Publish\Core\Base\Exceptions\BadStateException;
34
use eZ\Publish\Core\Base\Exceptions\UnauthorizedException;
35
use Exception;
36
use Psr\Log\LoggerInterface;
37
use Psr\Log\NullLogger;
38
39
/**
40
 * Location service, used for complex subtree operations.
41
 *
42
 * @example Examples/location.php
43
 */
44
class LocationService implements LocationServiceInterface
45
{
46
    /**
47
     * @var \eZ\Publish\Core\Repository\Repository
48
     */
49
    protected $repository;
50
51
    /**
52
     * @var \eZ\Publish\SPI\Persistence\Handler
53
     */
54
    protected $persistenceHandler;
55
56
    /**
57
     * @var array
58
     */
59
    protected $settings;
60
61
    /**
62
     * @var \eZ\Publish\Core\Repository\Helper\DomainMapper
63
     */
64
    protected $domainMapper;
65
66
    /**
67
     * @var \eZ\Publish\Core\Repository\Helper\NameSchemaService
68
     */
69
    protected $nameSchemaService;
70
71
    /**
72
     * @var \eZ\Publish\API\Repository\PermissionCriterionResolver
73
     */
74
    protected $permissionCriterionResolver;
75
76
    /**
77
     * @var \Psr\Log\LoggerInterface
78
     */
79
    private $logger;
80
81
    /**
82
     * Setups service with reference to repository object that created it & corresponding handler.
83
     *
84
     * @param \eZ\Publish\API\Repository\Repository $repository
85
     * @param \eZ\Publish\SPI\Persistence\Handler $handler
86
     * @param \eZ\Publish\Core\Repository\Helper\DomainMapper $domainMapper
87
     * @param \eZ\Publish\Core\Repository\Helper\NameSchemaService $nameSchemaService
88
     * @param \eZ\Publish\API\Repository\PermissionCriterionResolver $permissionCriterionResolver
89
     * @param array $settings
90
     * @param \Psr\Log\LoggerInterface|null $logger
91
     */
92
    public function __construct(
93
        RepositoryInterface $repository,
94
        Handler $handler,
95
        Helper\DomainMapper $domainMapper,
96
        Helper\NameSchemaService $nameSchemaService,
97
        PermissionCriterionResolver $permissionCriterionResolver,
98
        array $settings = array(),
99
        LoggerInterface $logger = null
100
    ) {
101
        $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...
102
        $this->persistenceHandler = $handler;
103
        $this->domainMapper = $domainMapper;
104
        $this->nameSchemaService = $nameSchemaService;
105
        // Union makes sure default settings are ignored if provided in argument
106
        $this->settings = $settings + array(
107
            //'defaultSetting' => array(),
108
        );
109
        $this->permissionCriterionResolver = $permissionCriterionResolver;
110
        $this->logger = null !== $logger ? $logger : new NullLogger();
111
    }
112
113
    /**
114
     * Copies the subtree starting from $subtree as a new subtree of $targetLocation.
115
     *
116
     * Only the items on which the user has read access are copied.
117
     *
118
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed copy the subtree to the given parent location
119
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user does not have read access to the whole source subtree
120
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the target location is a sub location of the given location
121
     *
122
     * @param \eZ\Publish\API\Repository\Values\Content\Location $subtree - the subtree denoted by the location to copy
123
     * @param \eZ\Publish\API\Repository\Values\Content\Location $targetParentLocation - the target parent location for the copy operation
124
     *
125
     * @return \eZ\Publish\API\Repository\Values\Content\Location The newly created location of the copied subtree
126
     */
127
    public function copySubtree(APILocation $subtree, APILocation $targetParentLocation)
128
    {
129
        $loadedSubtree = $this->loadLocation($subtree->id);
130
        $loadedTargetLocation = $this->loadLocation($targetParentLocation->id);
131
132
        if (stripos($loadedTargetLocation->pathString, $loadedSubtree->pathString) !== false) {
133
            throw new InvalidArgumentException('targetParentLocation', 'target parent location is a sub location of the given subtree');
134
        }
135
136
        // check create permission on target
137
        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...
138
            throw new UnauthorizedException('content', 'create');
139
        }
140
141
        /** Check read access to whole source subtree
142
         * @var bool|\eZ\Publish\API\Repository\Values\Content\Query\Criterion
143
         */
144
        $contentReadCriterion = $this->permissionCriterionResolver->getPermissionsCriterion();
0 ignored issues
show
Bug introduced by
The call to getPermissionsCriterion() misses some required arguments starting with $module.
Loading history...
145 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...
146
            throw new UnauthorizedException('content', 'read');
147
        } elseif ($contentReadCriterion !== true) {
148
            // Query if there are any content in subtree current user don't have access to
149
            $query = new Query(
150
                array(
151
                    'limit' => 0,
152
                    'filter' => new CriterionLogicalAnd(
153
                        array(
154
                            new CriterionSubtree($loadedSubtree->pathString),
155
                            new CriterionLogicalNot($contentReadCriterion),
0 ignored issues
show
Bug introduced by
It seems like $contentReadCriterion defined by $this->permissionCriteri...tPermissionsCriterion() on line 144 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...
156
                        )
157
                    ),
158
                )
159
            );
160
            $result = $this->repository->getSearchService()->findContent($query, array(), false);
161
            if ($result->totalCount > 0) {
162
                throw new UnauthorizedException('content', 'read');
163
            }
164
        }
165
166
        $this->repository->beginTransaction();
167
        try {
168
            $newLocation = $this->persistenceHandler->locationHandler()->copySubtree(
169
                $loadedSubtree->id,
170
                $loadedTargetLocation->id
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->buildLocationWithContent($newLocation, $content);
198
    }
199
200
    /**
201
     * {@inheritdoc}
202
     */
203
    public function loadLocation($locationId, array $prioritizedLanguages = null)
204
    {
205
        $spiLocation = $this->persistenceHandler->locationHandler()->load($locationId);
206
        $location = $this->domainMapper->buildLocation($spiLocation, $prioritizedLanguages ?: []);
207
        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...
208
            throw new UnauthorizedException('content', 'read');
209
        }
210
211
        return $location;
212
    }
213
214
    /**
215
     * {@inheritdoc}
216
     */
217
    public function loadLocationByRemoteId($remoteId, array $prioritizedLanguages = null)
218
    {
219
        if (!is_string($remoteId)) {
220
            throw new InvalidArgumentValue('remoteId', $remoteId);
221
        }
222
223
        $spiLocation = $this->persistenceHandler->locationHandler()->loadByRemoteId($remoteId);
224
        $location = $this->domainMapper->buildLocation($spiLocation, $prioritizedLanguages ?: []);
225
        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...
226
            throw new UnauthorizedException('content', 'read');
227
        }
228
229
        return $location;
230
    }
231
232
    /**
233
     * {@inheritdoc}
234
     */
235
    public function loadLocations(ContentInfo $contentInfo, APILocation $rootLocation = null, array $prioritizedLanguages = null)
236
    {
237
        if (!$contentInfo->published) {
238
            throw new BadStateException('$contentInfo', 'ContentInfo has no published versions');
239
        }
240
241
        $spiLocations = $this->persistenceHandler->locationHandler()->loadLocationsByContent(
242
            $contentInfo->id,
243
            $rootLocation !== null ? $rootLocation->id : null
244
        );
245
246
        $locations = [];
247
        $spiInfo = $this->persistenceHandler->contentHandler()->loadContentInfo($contentInfo->id);
248
        $content = $this->domainMapper->buildContentProxy($spiInfo, $prioritizedLanguages ?: []);
249
        foreach ($spiLocations as $spiLocation) {
250
            $location = $this->domainMapper->buildLocationWithContent($spiLocation, $content, $spiInfo);
251
            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...
252
                $locations[] = $location;
253
            }
254
        }
255
256
        return $locations;
257
    }
258
259
    /**
260
     * {@inheritdoc}
261
     */
262
    public function loadLocationChildren(APILocation $location, $offset = 0, $limit = 25, array $prioritizedLanguages = null)
263
    {
264
        if (!$this->domainMapper->isValidLocationSortField($location->sortField)) {
265
            throw new InvalidArgumentValue('sortField', $location->sortField, 'Location');
266
        }
267
268
        if (!$this->domainMapper->isValidLocationSortOrder($location->sortOrder)) {
269
            throw new InvalidArgumentValue('sortOrder', $location->sortOrder, 'Location');
270
        }
271
272
        if (!is_int($offset)) {
273
            throw new InvalidArgumentValue('offset', $offset);
274
        }
275
276
        if (!is_int($limit)) {
277
            throw new InvalidArgumentValue('limit', $limit);
278
        }
279
280
        $childLocations = array();
281
        $searchResult = $this->searchChildrenLocations($location, $offset, $limit, $prioritizedLanguages ?: []);
282
        foreach ($searchResult->searchHits as $searchHit) {
283
            $childLocations[] = $searchHit->valueObject;
284
        }
285
286
        return new LocationList(
287
            array(
288
                'locations' => $childLocations,
289
                'totalCount' => $searchResult->totalCount,
290
            )
291
        );
292
    }
293
294
    /**
295
     * {@inheritdoc}
296
     */
297
    public function loadParentLocationsForDraftContent(VersionInfo $versionInfo, array $prioritizedLanguages = null)
298
    {
299
        if (!$versionInfo->isDraft()) {
300
            throw new BadStateException(
301
                '$contentInfo',
302
                sprintf(
303
                    'Content [%d] %s has been already published. Use LocationService::loadLocations instead.',
304
                    $versionInfo->contentInfo->id,
305
                    $versionInfo->contentInfo->name
306
                )
307
            );
308
        }
309
310
        $spiLocations = $this->persistenceHandler
311
            ->locationHandler()
312
            ->loadParentLocationsForDraftContent($versionInfo->contentInfo->id);
313
314
        $contentIds = [];
315
        foreach ($spiLocations as $spiLocation) {
316
            $contentIds[] = $spiLocation->contentId;
317
        }
318
319
        $locations = [];
320
        $permissionResolver = $this->repository->getPermissionResolver();
321
        $spiContentInfoList = $this->persistenceHandler->contentHandler()->loadContentInfoList($contentIds);
322
        $contentList = $this->domainMapper->buildContentProxyList($spiContentInfoList, $prioritizedLanguages ?: []);
323
        foreach ($spiLocations as $spiLocation) {
324
            $location = $this->domainMapper->buildLocationWithContent(
325
                $spiLocation,
326
                $contentList[$spiLocation->contentId],
327
                $spiContentInfoList[$spiLocation->contentId]
328
            );
329
330
            if ($permissionResolver->canUser('content', 'read', $location->getContentInfo(), [$location])) {
331
                $locations[] = $location;
332
            }
333
        }
334
335
        return $locations;
336
    }
337
338
    /**
339
     * Returns the number of children which are readable by the current user of a location object.
340
     *
341
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
342
     *
343
     * @return int
344
     */
345
    public function getLocationChildCount(APILocation $location)
346
    {
347
        $searchResult = $this->searchChildrenLocations($location, 0, 0);
348
349
        return $searchResult->totalCount;
350
    }
351
352
    /**
353
     * Searches children locations of the provided parent location id.
354
     *
355
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
356
     * @param int $offset
357
     * @param int $limit
358
     *
359
     * @return \eZ\Publish\API\Repository\Values\Content\Search\SearchResult
360
     */
361
    protected function searchChildrenLocations(APILocation $location, $offset = 0, $limit = -1, array $prioritizedLanguages = null)
362
    {
363
        $query = new LocationQuery([
364
            'filter' => new Criterion\ParentLocationId($location->id),
365
            'offset' => $offset >= 0 ? (int)$offset : 0,
366
            'limit' => $limit >= 0 ? (int)$limit : null,
367
            'sortClauses' => $location->getSortClauses(),
368
        ]);
369
370
        return $this->repository->getSearchService()->findLocations($query, ['languages' => $prioritizedLanguages]);
371
    }
372
373
    /**
374
     * Creates the new $location in the content repository for the given content.
375
     *
376
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed to create this location
377
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the content is already below the specified parent
378
     *                                        or the parent is a sub location of the location of the content
379
     *                                        or if set the remoteId exists already
380
     *
381
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
382
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct $locationCreateStruct
383
     *
384
     * @return \eZ\Publish\API\Repository\Values\Content\Location the newly created Location
385
     */
386
    public function createLocation(ContentInfo $contentInfo, LocationCreateStruct $locationCreateStruct)
387
    {
388
        $content = $this->repository->getContentService()->loadContent($contentInfo->id);
389
        $parentLocation = $this->loadLocation($locationCreateStruct->parentLocationId);
390
391
        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...
392
            throw new UnauthorizedException('content', 'manage_locations');
393
        }
394
395
        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...
396
            throw new UnauthorizedException('content', 'create');
397
        }
398
399
        // Check if the parent is a sub location of one of the existing content locations (this also solves the
400
        // situation where parent location actually one of the content locations),
401
        // or if the content already has location below given location create struct parent
402
        $existingContentLocations = $this->loadLocations($content->contentInfo);
403
        if (!empty($existingContentLocations)) {
404
            foreach ($existingContentLocations as $existingContentLocation) {
405
                if (stripos($parentLocation->pathString, $existingContentLocation->pathString) !== false) {
406
                    throw new InvalidArgumentException(
407
                        '$locationCreateStruct',
408
                        'Specified parent is a sub location of one of the existing content locations.'
409
                    );
410
                }
411
                if ($parentLocation->id == $existingContentLocation->parentLocationId) {
412
                    throw new InvalidArgumentException(
413
                        '$locationCreateStruct',
414
                        'Content is already below the specified parent.'
415
                    );
416
                }
417
            }
418
        }
419
420
        $spiLocationCreateStruct = $this->domainMapper->buildSPILocationCreateStruct(
421
            $locationCreateStruct,
422
            $parentLocation,
423
            $content->contentInfo->mainLocationId !== null ? $content->contentInfo->mainLocationId : true,
424
            $content->contentInfo->id,
425
            $content->contentInfo->currentVersionNo
426
        );
427
428
        $this->repository->beginTransaction();
429
        try {
430
            $newLocation = $this->persistenceHandler->locationHandler()->create($spiLocationCreateStruct);
431
432
            $urlAliasNames = $this->nameSchemaService->resolveUrlAliasSchema($content);
433 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...
434
                $this->persistenceHandler->urlAliasHandler()->publishUrlAliasForLocation(
435
                    $newLocation->id,
436
                    $newLocation->parentId,
437
                    $name,
438
                    $languageCode,
439
                    $content->contentInfo->alwaysAvailable,
440
                    // @todo: this is legacy storage specific for updating ezcontentobject_tree.path_identification_string, to be removed
441
                    $languageCode === $content->contentInfo->mainLanguageCode
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $languageCode (integer) and $content->contentInfo->mainLanguageCode (string) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
442
                );
443
            }
444
445
            $this->repository->commit();
446
        } catch (Exception $e) {
447
            $this->repository->rollback();
448
            throw $e;
449
        }
450
451
        return $this->domainMapper->buildLocationWithContent($newLocation, $content);
452
    }
453
454
    /**
455
     * Updates $location in the content repository.
456
     *
457
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed to update this location
458
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException   if if set the remoteId exists already
459
     *
460
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
461
     * @param \eZ\Publish\API\Repository\Values\Content\LocationUpdateStruct $locationUpdateStruct
462
     *
463
     * @return \eZ\Publish\API\Repository\Values\Content\Location the updated Location
464
     */
465
    public function updateLocation(APILocation $location, LocationUpdateStruct $locationUpdateStruct)
466
    {
467
        if (!$this->domainMapper->isValidLocationPriority($locationUpdateStruct->priority)) {
468
            throw new InvalidArgumentValue('priority', $locationUpdateStruct->priority, 'LocationUpdateStruct');
469
        }
470
471
        if ($locationUpdateStruct->remoteId !== null && (!is_string($locationUpdateStruct->remoteId) || empty($locationUpdateStruct->remoteId))) {
472
            throw new InvalidArgumentValue('remoteId', $locationUpdateStruct->remoteId, 'LocationUpdateStruct');
473
        }
474
475
        if ($locationUpdateStruct->sortField !== null && !$this->domainMapper->isValidLocationSortField($locationUpdateStruct->sortField)) {
476
            throw new InvalidArgumentValue('sortField', $locationUpdateStruct->sortField, 'LocationUpdateStruct');
477
        }
478
479
        if ($locationUpdateStruct->sortOrder !== null && !$this->domainMapper->isValidLocationSortOrder($locationUpdateStruct->sortOrder)) {
480
            throw new InvalidArgumentValue('sortOrder', $locationUpdateStruct->sortOrder, 'LocationUpdateStruct');
481
        }
482
483
        $loadedLocation = $this->loadLocation($location->id);
484
485
        if ($locationUpdateStruct->remoteId !== null) {
486
            try {
487
                $existingLocation = $this->loadLocationByRemoteId($locationUpdateStruct->remoteId);
488
                if ($existingLocation !== null && $existingLocation->id !== $loadedLocation->id) {
489
                    throw new InvalidArgumentException('locationUpdateStruct', 'location with provided remote ID already exists');
490
                }
491
            } catch (APINotFoundException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
492
            }
493
        }
494
495
        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...
496
            throw new UnauthorizedException('content', 'edit');
497
        }
498
499
        $updateStruct = new UpdateStruct();
500
        $updateStruct->priority = $locationUpdateStruct->priority !== null ? $locationUpdateStruct->priority : $loadedLocation->priority;
501
        $updateStruct->remoteId = $locationUpdateStruct->remoteId !== null ? trim($locationUpdateStruct->remoteId) : $loadedLocation->remoteId;
502
        $updateStruct->sortField = $locationUpdateStruct->sortField !== null ? $locationUpdateStruct->sortField : $loadedLocation->sortField;
503
        $updateStruct->sortOrder = $locationUpdateStruct->sortOrder !== null ? $locationUpdateStruct->sortOrder : $loadedLocation->sortOrder;
504
505
        $this->repository->beginTransaction();
506
        try {
507
            $this->persistenceHandler->locationHandler()->update($updateStruct, $loadedLocation->id);
508
            $this->repository->commit();
509
        } catch (Exception $e) {
510
            $this->repository->rollback();
511
            throw $e;
512
        }
513
514
        return $this->loadLocation($loadedLocation->id);
515
    }
516
517
    /**
518
     * Swaps the contents held by $location1 and $location2.
519
     *
520
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed to swap content
521
     *
522
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location1
523
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location2
524
     */
525
    public function swapLocation(APILocation $location1, APILocation $location2)
526
    {
527
        $loadedLocation1 = $this->loadLocation($location1->id);
528
        $loadedLocation2 = $this->loadLocation($location2->id);
529
530
        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...
531
            throw new UnauthorizedException('content', 'edit');
532
        }
533
        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...
534
            throw new UnauthorizedException('content', 'edit');
535
        }
536
537
        $this->repository->beginTransaction();
538
        try {
539
            $this->persistenceHandler->locationHandler()->swap($loadedLocation1->id, $loadedLocation2->id);
540
            $this->persistenceHandler->urlAliasHandler()->locationSwapped(
541
                $location1->id,
542
                $location1->parentLocationId,
543
                $location2->id,
544
                $location2->parentLocationId
545
            );
546
            $this->persistenceHandler->bookmarkHandler()->locationSwapped($loadedLocation1->id, $loadedLocation2->id);
547
            $this->repository->commit();
548
        } catch (Exception $e) {
549
            $this->repository->rollback();
550
            throw $e;
551
        }
552
    }
553
554
    /**
555
     * Hides the $location and marks invisible all descendants of $location.
556
     *
557
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed to hide this location
558
     *
559
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
560
     *
561
     * @return \eZ\Publish\API\Repository\Values\Content\Location $location, with updated hidden value
562
     */
563 View Code Duplication
    public function hideLocation(APILocation $location)
564
    {
565
        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...
566
            throw new UnauthorizedException('content', 'hide');
567
        }
568
569
        $this->repository->beginTransaction();
570
        try {
571
            $this->persistenceHandler->locationHandler()->hide($location->id);
572
            $this->repository->commit();
573
        } catch (Exception $e) {
574
            $this->repository->rollback();
575
            throw $e;
576
        }
577
578
        return $this->loadLocation($location->id);
579
    }
580
581
    /**
582
     * Unhides the $location.
583
     *
584
     * This method and marks visible all descendants of $locations
585
     * until a hidden location is found.
586
     *
587
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed to unhide this location
588
     *
589
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
590
     *
591
     * @return \eZ\Publish\API\Repository\Values\Content\Location $location, with updated hidden value
592
     */
593 View Code Duplication
    public function unhideLocation(APILocation $location)
594
    {
595
        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...
596
            throw new UnauthorizedException('content', 'hide');
597
        }
598
599
        $this->repository->beginTransaction();
600
        try {
601
            $this->persistenceHandler->locationHandler()->unHide($location->id);
602
            $this->repository->commit();
603
        } catch (Exception $e) {
604
            $this->repository->rollback();
605
            throw $e;
606
        }
607
608
        return $this->loadLocation($location->id);
609
    }
610
611
    /**
612
     * Moves the subtree to $newParentLocation.
613
     *
614
     * If a user has the permission to move the location to a target location
615
     * he can do it regardless of an existing descendant on which the user has no permission.
616
     *
617
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed to move this location to the target
618
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user does not have read access to the whole source subtree
619
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException If the new parent is in a subtree of the location
620
     *
621
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
622
     * @param \eZ\Publish\API\Repository\Values\Content\Location $newParentLocation
623
     */
624
    public function moveSubtree(APILocation $location, APILocation $newParentLocation)
625
    {
626
        $location = $this->loadLocation($location->id);
627
        $newParentLocation = $this->loadLocation($newParentLocation->id);
628
629
        // check create permission on target location
630
        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...
631
            throw new UnauthorizedException('content', 'create');
632
        }
633
634
        /** Check read access to whole source subtree
635
         * @var bool|\eZ\Publish\API\Repository\Values\Content\Query\Criterion
636
         */
637
        $contentReadCriterion = $this->permissionCriterionResolver->getPermissionsCriterion();
0 ignored issues
show
Bug introduced by
The call to getPermissionsCriterion() misses some required arguments starting with $module.
Loading history...
638 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...
639
            throw new UnauthorizedException('content', 'read');
640
        } elseif ($contentReadCriterion !== true) {
641
            // Query if there are any content in subtree current user don't have access to
642
            $query = new Query(
643
                array(
644
                    'limit' => 0,
645
                    'filter' => new CriterionLogicalAnd(
646
                        array(
647
                            new CriterionSubtree($location->pathString),
648
                            new CriterionLogicalNot($contentReadCriterion),
0 ignored issues
show
Bug introduced by
It seems like $contentReadCriterion defined by $this->permissionCriteri...tPermissionsCriterion() on line 637 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...
649
                        )
650
                    ),
651
                )
652
            );
653
            $result = $this->repository->getSearchService()->findContent($query, array(), false);
654
            if ($result->totalCount > 0) {
655
                throw new UnauthorizedException('content', 'read');
656
            }
657
        }
658
659
        if (strpos($newParentLocation->pathString, $location->pathString) === 0) {
660
            throw new InvalidArgumentException(
661
                '$newParentLocation',
662
                'new parent location is in a subtree of the given $location'
663
            );
664
        }
665
666
        $this->repository->beginTransaction();
667
        try {
668
            $this->persistenceHandler->locationHandler()->move($location->id, $newParentLocation->id);
669
670
            $content = $this->repository->getContentService()->loadContent($location->contentId);
671
            $urlAliasNames = $this->nameSchemaService->resolveUrlAliasSchema($content);
672 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...
673
                $this->persistenceHandler->urlAliasHandler()->publishUrlAliasForLocation(
674
                    $location->id,
675
                    $newParentLocation->id,
676
                    $name,
677
                    $languageCode,
678
                    $content->contentInfo->alwaysAvailable
679
                );
680
            }
681
682
            $this->persistenceHandler->urlAliasHandler()->locationMoved(
683
                $location->id,
684
                $location->parentLocationId,
685
                $newParentLocation->id
686
            );
687
688
            $this->repository->commit();
689
        } catch (Exception $e) {
690
            $this->repository->rollback();
691
            throw $e;
692
        }
693
    }
694
695
    /**
696
     * Deletes $location and all its descendants.
697
     *
698
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user is not allowed to delete this location or a descendant
699
     *
700
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
701
     */
702
    public function deleteLocation(APILocation $location)
703
    {
704
        $location = $this->loadLocation($location->id);
705
706
        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...
707
            throw new UnauthorizedException('content', 'manage_locations');
708
        }
709
        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...
710
            throw new UnauthorizedException('content', 'remove');
711
        }
712
713
        /** Check remove access to descendants
714
         * @var bool|\eZ\Publish\API\Repository\Values\Content\Query\Criterion
715
         */
716
        $contentReadCriterion = $this->permissionCriterionResolver->getPermissionsCriterion('content', 'remove');
717 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...
718
            throw new UnauthorizedException('content', 'remove');
719
        } elseif ($contentReadCriterion !== true) {
720
            // Query if there are any content in subtree current user don't have access to
721
            $query = new Query(
722
                array(
723
                    'limit' => 0,
724
                    'filter' => new CriterionLogicalAnd(
725
                        array(
726
                            new CriterionSubtree($location->pathString),
727
                            new CriterionLogicalNot($contentReadCriterion),
0 ignored issues
show
Bug introduced by
It seems like $contentReadCriterion defined by $this->permissionCriteri...on('content', 'remove') on line 716 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...
728
                        )
729
                    ),
730
                )
731
            );
732
            $result = $this->repository->getSearchService()->findContent($query, array(), false);
733
            if ($result->totalCount > 0) {
734
                throw new UnauthorizedException('content', 'remove');
735
            }
736
        }
737
738
        $this->repository->beginTransaction();
739
        try {
740
            $this->persistenceHandler->locationHandler()->removeSubtree($location->id);
741
            $this->persistenceHandler->urlAliasHandler()->locationDeleted($location->id);
742
            $this->repository->commit();
743
        } catch (Exception $e) {
744
            $this->repository->rollback();
745
            throw $e;
746
        }
747
    }
748
749
    /**
750
     * Instantiates a new location create class.
751
     *
752
     * @param mixed $parentLocationId the parent under which the new location should be created
753
     *
754
     * @return \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct
755
     */
756
    public function newLocationCreateStruct($parentLocationId)
757
    {
758
        return new LocationCreateStruct(
759
            array(
760
                'parentLocationId' => $parentLocationId,
761
            )
762
        );
763
    }
764
765
    /**
766
     * Instantiates a new location update class.
767
     *
768
     * @return \eZ\Publish\API\Repository\Values\Content\LocationUpdateStruct
769
     */
770
    public function newLocationUpdateStruct()
771
    {
772
        return new LocationUpdateStruct();
773
    }
774
775
    /**
776
     * Get the total number of all existing Locations. Can be combined with loadAllLocations.
777
     *
778
     * @see loadAllLocations
779
     *
780
     * @return int Total number of Locations
781
     */
782
    public function getAllLocationsCount(): int
783
    {
784
        return $this->persistenceHandler->locationHandler()->countAllLocations();
785
    }
786
787
    /**
788
     * Bulk-load all existing Locations, constrained by $limit and $offset to paginate results.
789
     *
790
     * @param int $offset
791
     * @param int $limit
792
     *
793
     * @return \eZ\Publish\API\Repository\Values\Content\Location[]
794
     *
795
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
796
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
797
     */
798
    public function loadAllLocations(int $offset = 0, int $limit = 25): array
799
    {
800
        $spiLocations = $this->persistenceHandler->locationHandler()->loadAllLocations(
801
            $offset,
802
            $limit
803
        );
804
        $contentIds = array_unique(
805
            array_map(
806
                function (SPILocation $spiLocation) {
807
                    return $spiLocation->contentId;
808
                },
809
                $spiLocations
810
            )
811
        );
812
813
        $permissionResolver = $this->repository->getPermissionResolver();
814
        $spiContentInfoList = $this->persistenceHandler->contentHandler()->loadContentInfoList(
815
            $contentIds
816
        );
817
        $contentList = $this->domainMapper->buildContentProxyList(
818
            $spiContentInfoList,
819
            Language::ALL,
820
            false
821
        );
822
        $locations = [];
823
        foreach ($spiLocations as $spiLocation) {
824
            if (!isset($spiContentInfoList[$spiLocation->contentId], $contentList[$spiLocation->contentId])) {
825
                $this->logger->warning(
826
                    sprintf(
827
                        'Location %d has missing Content %d',
828
                        $spiLocation->id,
829
                        $spiLocation->contentId
830
                    )
831
                );
832
                continue;
833
            }
834
835
            $location = $this->domainMapper->buildLocationWithContent(
836
                $spiLocation,
837
                $contentList[$spiLocation->contentId],
838
                $spiContentInfoList[$spiLocation->contentId]
839
            );
840
841
            $contentInfo = $location->getContentInfo();
842
            if (!$permissionResolver->canUser('content', 'read', $contentInfo, [$location])) {
843
                continue;
844
            }
845
            $locations[] = $location;
846
        }
847
848
        return $locations;
849
    }
850
}
851