Completed
Push — EZP-30796 ( f39111...1b67d5 )
by
unknown
24:32
created

LocationService::createLocation()   B

Complexity

Conditions 9
Paths 20

Size

Total Lines 74

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
nc 20
nop 2
dl 0
loc 74
rs 7.0117
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
use eZ\Publish\API\Repository\Values\ContentType\ContentType;
39
40
/**
41
 * Location service, used for complex subtree operations.
42
 *
43
 * @example Examples/location.php
44
 */
45
class LocationService implements LocationServiceInterface
46
{
47
    /** @var \eZ\Publish\Core\Repository\Repository */
48
    protected $repository;
49
50
    /** @var \eZ\Publish\SPI\Persistence\Handler */
51
    protected $persistenceHandler;
52
53
    /** @var array */
54
    protected $settings;
55
56
    /** @var \eZ\Publish\Core\Repository\Helper\DomainMapper */
57
    protected $domainMapper;
58
59
    /** @var \eZ\Publish\Core\Repository\Helper\NameSchemaService */
60
    protected $nameSchemaService;
61
62
    /** @var \eZ\Publish\API\Repository\PermissionCriterionResolver */
63
    protected $permissionCriterionResolver;
64
65
    /** @var \Psr\Log\LoggerInterface */
66
    private $logger;
67
68
    /**
69
     * Setups service with reference to repository object that created it & corresponding handler.
70
     *
71
     * @param \eZ\Publish\API\Repository\Repository $repository
72
     * @param \eZ\Publish\SPI\Persistence\Handler $handler
73
     * @param \eZ\Publish\Core\Repository\Helper\DomainMapper $domainMapper
74
     * @param \eZ\Publish\Core\Repository\Helper\NameSchemaService $nameSchemaService
75
     * @param \eZ\Publish\API\Repository\PermissionCriterionResolver $permissionCriterionResolver
76
     * @param array $settings
77
     * @param \Psr\Log\LoggerInterface|null $logger
78
     */
79
    public function __construct(
80
        RepositoryInterface $repository,
81
        Handler $handler,
82
        Helper\DomainMapper $domainMapper,
83
        Helper\NameSchemaService $nameSchemaService,
84
        PermissionCriterionResolver $permissionCriterionResolver,
85
        array $settings = [],
86
        LoggerInterface $logger = null
87
    ) {
88
        $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...
89
        $this->persistenceHandler = $handler;
90
        $this->domainMapper = $domainMapper;
91
        $this->nameSchemaService = $nameSchemaService;
92
        // Union makes sure default settings are ignored if provided in argument
93
        $this->settings = $settings + [
94
            //'defaultSetting' => array(),
95
        ];
96
        $this->permissionCriterionResolver = $permissionCriterionResolver;
97
        $this->logger = null !== $logger ? $logger : new NullLogger();
98
    }
99
100
    /**
101
     * Copies the subtree starting from $subtree as a new subtree of $targetLocation.
102
     *
103
     * Only the items on which the user has read access are copied.
104
     *
105
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed copy the subtree to the given parent location
106
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user does not have read access to the whole source subtree
107
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the target location is a sub location of the given location
108
     *
109
     * @param \eZ\Publish\API\Repository\Values\Content\Location $subtree - the subtree denoted by the location to copy
110
     * @param \eZ\Publish\API\Repository\Values\Content\Location $targetParentLocation - the target parent location for the copy operation
111
     *
112
     * @return \eZ\Publish\API\Repository\Values\Content\Location The newly created location of the copied subtree
113
     */
114
    public function copySubtree(APILocation $subtree, APILocation $targetParentLocation)
115
    {
116
        $loadedSubtree = $this->loadLocation($subtree->id);
117
        $loadedTargetLocation = $this->loadLocation($targetParentLocation->id);
118
119
        if (stripos($loadedTargetLocation->pathString, $loadedSubtree->pathString) !== false) {
120
            throw new InvalidArgumentException('targetParentLocation', 'target parent location is a sub location of the given subtree');
121
        }
122
123
        // check create permission on target
124
        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...
125
            throw new UnauthorizedException('content', 'create', ['locationId' => $loadedTargetLocation->id]);
126
        }
127
128
        /** Check read access to whole source subtree
129
         * @var bool|\eZ\Publish\API\Repository\Values\Content\Query\Criterion
130
         */
131
        $contentReadCriterion = $this->permissionCriterionResolver->getPermissionsCriterion();
0 ignored issues
show
Bug introduced by
The call to getPermissionsCriterion() misses some required arguments starting with $module.
Loading history...
132 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...
133
            throw new UnauthorizedException('content', 'read');
134
        } elseif ($contentReadCriterion !== true) {
135
            // Query if there are any content in subtree current user don't have access to
136
            $query = new Query(
137
                [
138
                    'limit' => 0,
139
                    'filter' => new CriterionLogicalAnd(
140
                        [
141
                            new CriterionSubtree($loadedSubtree->pathString),
142
                            new CriterionLogicalNot($contentReadCriterion),
0 ignored issues
show
Bug introduced by
It seems like $contentReadCriterion defined by $this->permissionCriteri...tPermissionsCriterion() on line 131 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...
143
                        ]
144
                    ),
145
                ]
146
            );
147
            $result = $this->repository->getSearchService()->findContent($query, [], false);
148
            if ($result->totalCount > 0) {
149
                throw new UnauthorizedException('content', 'read');
150
            }
151
        }
152
153
        $this->repository->beginTransaction();
154
        try {
155
            $newLocation = $this->persistenceHandler->locationHandler()->copySubtree(
156
                $loadedSubtree->id,
157
                $loadedTargetLocation->id,
158
                $this->repository->getPermissionResolver()->getCurrentUserReference()->getUserId()
159
            );
160
161
            $content = $this->repository->getContentService()->loadContent($newLocation->contentId);
162
            $urlAliasNames = $this->nameSchemaService->resolveUrlAliasSchema($content);
163 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...
164
                $this->persistenceHandler->urlAliasHandler()->publishUrlAliasForLocation(
165
                    $newLocation->id,
166
                    $loadedTargetLocation->id,
167
                    $name,
168
                    $languageCode,
169
                    $content->contentInfo->alwaysAvailable
170
                );
171
            }
172
173
            $this->persistenceHandler->urlAliasHandler()->locationCopied(
174
                $loadedSubtree->id,
175
                $newLocation->id,
176
                $loadedTargetLocation->id
177
            );
178
179
            $this->repository->commit();
180
        } catch (Exception $e) {
181
            $this->repository->rollback();
182
            throw $e;
183
        }
184
185
        return $this->domainMapper->buildLocationWithContent($newLocation, $content);
186
    }
187
188
    /**
189
     * {@inheritdoc}
190
     */
191
    public function loadLocation($locationId, array $prioritizedLanguages = null, bool $useAlwaysAvailable = null)
192
    {
193
        $spiLocation = $this->persistenceHandler->locationHandler()->load($locationId, $prioritizedLanguages, $useAlwaysAvailable ?? true);
0 ignored issues
show
Bug introduced by
It seems like $prioritizedLanguages defined by parameter $prioritizedLanguages on line 191 can also be of type array; however, eZ\Publish\SPI\Persisten...ocation\Handler::load() does only seem to accept null|array<integer,string>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
194
        $location = $this->domainMapper->buildLocation($spiLocation, $prioritizedLanguages ?: [], $useAlwaysAvailable ?? true);
195
        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...
196
            throw new UnauthorizedException('content', 'read', ['locationId' => $location->id]);
197
        }
198
199
        return $location;
200
    }
201
202
    /**
203
     * {@inheritdoc}
204
     */
205
    public function loadLocationList(array $locationIds, array $prioritizedLanguages = null, bool $useAlwaysAvailable = null): iterable
206
    {
207
        $spiLocations = $this->persistenceHandler->locationHandler()->loadList(
208
            $locationIds,
209
            $prioritizedLanguages,
0 ignored issues
show
Bug introduced by
It seems like $prioritizedLanguages defined by parameter $prioritizedLanguages on line 205 can also be of type array; however, eZ\Publish\SPI\Persisten...ion\Handler::loadList() does only seem to accept null|array<integer,string>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
210
            $useAlwaysAvailable ?? true
211
        );
212
        if (empty($spiLocations)) {
213
            return [];
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array(); (array) is incompatible with the return type declared by the interface eZ\Publish\API\Repositor...rvice::loadLocationList of type eZ\Publish\API\Repositor...API\Repository\iterable.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
214
        }
215
216
        // Get content id's
217
        $contentIds = [];
218
        foreach ($spiLocations as $spiLocation) {
219
            $contentIds[] = $spiLocation->contentId;
220
        }
221
222
        // Load content info and Get content proxy
223
        $spiContentInfoList = $this->persistenceHandler->contentHandler()->loadContentInfoList($contentIds);
224
        $contentProxyList = $this->domainMapper->buildContentProxyList(
225
            $spiContentInfoList,
226
            $prioritizedLanguages ?? [],
227
            $useAlwaysAvailable ?? true
228
        );
229
230
        // Build locations using the bulk retrieved content info and bulk lazy loaded content proxies.
231
        $locations = [];
232
        $permissionResolver = $this->repository->getPermissionResolver();
233
        foreach ($spiLocations as $spiLocation) {
234
            $location = $this->domainMapper->buildLocationWithContent(
235
                $spiLocation,
236
                $contentProxyList[$spiLocation->contentId] ?? null,
237
                $spiContentInfoList[$spiLocation->contentId] ?? null
238
            );
239
240
            if ($permissionResolver->canUser('content', 'read', $location->getContentInfo(), [$location])) {
241
                $locations[$spiLocation->id] = $location;
242
            }
243
        }
244
245
        return $locations;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $locations; (array) is incompatible with the return type declared by the interface eZ\Publish\API\Repositor...rvice::loadLocationList of type eZ\Publish\API\Repositor...API\Repository\iterable.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
246
    }
247
248
    /**
249
     * {@inheritdoc}
250
     */
251
    public function loadLocationByRemoteId($remoteId, array $prioritizedLanguages = null, bool $useAlwaysAvailable = null)
252
    {
253
        if (!is_string($remoteId)) {
254
            throw new InvalidArgumentValue('remoteId', $remoteId);
255
        }
256
257
        $spiLocation = $this->persistenceHandler->locationHandler()->loadByRemoteId($remoteId, $prioritizedLanguages, $useAlwaysAvailable ?? true);
0 ignored issues
show
Bug introduced by
It seems like $prioritizedLanguages defined by parameter $prioritizedLanguages on line 251 can also be of type array; however, eZ\Publish\SPI\Persisten...ndler::loadByRemoteId() does only seem to accept null|array<integer,string>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
258
        $location = $this->domainMapper->buildLocation($spiLocation, $prioritizedLanguages ?: [], $useAlwaysAvailable ?? true);
259
        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...
260
            throw new UnauthorizedException('content', 'read', ['locationId' => $location->id]);
261
        }
262
263
        return $location;
264
    }
265
266
    /**
267
     * {@inheritdoc}
268
     */
269
    public function loadLocations(ContentInfo $contentInfo, APILocation $rootLocation = null, array $prioritizedLanguages = null)
270
    {
271
        if (!$contentInfo->published) {
272
            throw new BadStateException('$contentInfo', 'ContentInfo has no published versions');
273
        }
274
275
        $spiLocations = $this->persistenceHandler->locationHandler()->loadLocationsByContent(
276
            $contentInfo->id,
277
            $rootLocation !== null ? $rootLocation->id : null
278
        );
279
280
        $locations = [];
281
        $spiInfo = $this->persistenceHandler->contentHandler()->loadContentInfo($contentInfo->id);
282
        $content = $this->domainMapper->buildContentProxy($spiInfo, $prioritizedLanguages ?: []);
283
        foreach ($spiLocations as $spiLocation) {
284
            $location = $this->domainMapper->buildLocationWithContent($spiLocation, $content, $spiInfo);
285
            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...
286
                $locations[] = $location;
287
            }
288
        }
289
290
        return $locations;
291
    }
292
293
    /**
294
     * {@inheritdoc}
295
     */
296
    public function loadLocationChildren(APILocation $location, $offset = 0, $limit = 25, array $prioritizedLanguages = null)
297
    {
298
        if (!$this->domainMapper->isValidLocationSortField($location->sortField)) {
299
            throw new InvalidArgumentValue('sortField', $location->sortField, 'Location');
300
        }
301
302
        if (!$this->domainMapper->isValidLocationSortOrder($location->sortOrder)) {
303
            throw new InvalidArgumentValue('sortOrder', $location->sortOrder, 'Location');
304
        }
305
306
        if (!is_int($offset)) {
307
            throw new InvalidArgumentValue('offset', $offset);
308
        }
309
310
        if (!is_int($limit)) {
311
            throw new InvalidArgumentValue('limit', $limit);
312
        }
313
314
        $childLocations = [];
315
        $searchResult = $this->searchChildrenLocations($location, $offset, $limit, $prioritizedLanguages ?: []);
316
        foreach ($searchResult->searchHits as $searchHit) {
317
            $childLocations[] = $searchHit->valueObject;
318
        }
319
320
        return new LocationList(
321
            [
322
                'locations' => $childLocations,
323
                'totalCount' => $searchResult->totalCount,
324
            ]
325
        );
326
    }
327
328
    /**
329
     * {@inheritdoc}
330
     */
331
    public function loadParentLocationsForDraftContent(VersionInfo $versionInfo, array $prioritizedLanguages = null)
332
    {
333
        if (!$versionInfo->isDraft()) {
334
            throw new BadStateException(
335
                '$contentInfo',
336
                sprintf(
337
                    'Content [%d] %s has been already published. Use LocationService::loadLocations instead.',
338
                    $versionInfo->contentInfo->id,
339
                    $versionInfo->contentInfo->name
340
                )
341
            );
342
        }
343
344
        $spiLocations = $this->persistenceHandler
345
            ->locationHandler()
346
            ->loadParentLocationsForDraftContent($versionInfo->contentInfo->id);
347
348
        $contentIds = [];
349
        foreach ($spiLocations as $spiLocation) {
350
            $contentIds[] = $spiLocation->contentId;
351
        }
352
353
        $locations = [];
354
        $permissionResolver = $this->repository->getPermissionResolver();
355
        $spiContentInfoList = $this->persistenceHandler->contentHandler()->loadContentInfoList($contentIds);
356
        $contentList = $this->domainMapper->buildContentProxyList($spiContentInfoList, $prioritizedLanguages ?: []);
357
        foreach ($spiLocations as $spiLocation) {
358
            $location = $this->domainMapper->buildLocationWithContent(
359
                $spiLocation,
360
                $contentList[$spiLocation->contentId],
361
                $spiContentInfoList[$spiLocation->contentId]
362
            );
363
364
            if ($permissionResolver->canUser('content', 'read', $location->getContentInfo(), [$location])) {
365
                $locations[] = $location;
366
            }
367
        }
368
369
        return $locations;
370
    }
371
372
    /**
373
     * Returns the number of children which are readable by the current user of a location object.
374
     *
375
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
376
     *
377
     * @return int
378
     */
379
    public function getLocationChildCount(APILocation $location)
380
    {
381
        $searchResult = $this->searchChildrenLocations($location, 0, 0);
382
383
        return $searchResult->totalCount;
384
    }
385
386
    /**
387
     * Searches children locations of the provided parent location id.
388
     *
389
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
390
     * @param int $offset
391
     * @param int $limit
392
     *
393
     * @return \eZ\Publish\API\Repository\Values\Content\Search\SearchResult
394
     */
395
    protected function searchChildrenLocations(APILocation $location, $offset = 0, $limit = -1, array $prioritizedLanguages = null)
396
    {
397
        $query = new LocationQuery([
398
            'filter' => new Criterion\ParentLocationId($location->id),
399
            'offset' => $offset >= 0 ? (int)$offset : 0,
400
            'limit' => $limit >= 0 ? (int)$limit : null,
401
            'sortClauses' => $location->getSortClauses(),
402
        ]);
403
404
        return $this->repository->getSearchService()->findLocations($query, ['languages' => $prioritizedLanguages]);
405
    }
406
407
    /**
408
     * Creates the new $location in the content repository for the given content.
409
     *
410
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed to create this location
411
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the content is already below the specified parent
412
     *                                        or the parent is a sub location of the location of the content
413
     *                                        or if set the remoteId exists already
414
     *
415
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
416
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct $locationCreateStruct
417
     *
418
     * @return \eZ\Publish\API\Repository\Values\Content\Location the newly created Location
419
     */
420
    public function createLocation(ContentInfo $contentInfo, LocationCreateStruct $locationCreateStruct)
421
    {
422
        $content = $this->domainMapper->buildContentDomainObjectFromPersistence(
423
            $this->persistenceHandler->contentHandler()->load($contentInfo->id),
424
            $this->persistenceHandler->contentTypeHandler()->load($contentInfo->contentTypeId)
425
        );
426
427
        $parentLocation = $this->domainMapper->buildLocation(
428
            $this->persistenceHandler->locationHandler()->load($locationCreateStruct->parentLocationId)
429
        );
430
431
        $contentInfo = $content->contentInfo;
432
433
        if (!$this->repository->canUser('content', 'manage_locations', $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...
434
            throw new UnauthorizedException('content', 'manage_locations', ['contentId' => $contentInfo->id]);
435
        }
436
437
        if (!$this->repository->canUser('content', 'create', $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...
438
            throw new UnauthorizedException('content', 'create', ['locationId' => $parentLocation->id]);
439
        }
440
441
        // Check if the parent is a sub location of one of the existing content locations (this also solves the
442
        // situation where parent location actually one of the content locations),
443
        // or if the content already has location below given location create struct parent
444
        $existingContentLocations = $this->loadLocations($contentInfo);
445
        if (!empty($existingContentLocations)) {
446
            foreach ($existingContentLocations as $existingContentLocation) {
447
                if (stripos($parentLocation->pathString, $existingContentLocation->pathString) !== false) {
448
                    throw new InvalidArgumentException(
449
                        '$locationCreateStruct',
450
                        'Specified parent is a sub location of one of the existing content locations.'
451
                    );
452
                }
453
                if ($parentLocation->id == $existingContentLocation->parentLocationId) {
454
                    throw new InvalidArgumentException(
455
                        '$locationCreateStruct',
456
                        'Content is already below the specified parent.'
457
                    );
458
                }
459
            }
460
        }
461
462
        $spiLocationCreateStruct = $this->domainMapper->buildSPILocationCreateStruct(
463
            $locationCreateStruct,
464
            $parentLocation,
465
            $contentInfo->mainLocationId ?? true,
466
            $contentInfo->id,
467
            $contentInfo->currentVersionNo
468
        );
469
470
        $this->repository->beginTransaction();
471
        try {
472
            $newLocation = $this->persistenceHandler->locationHandler()->create($spiLocationCreateStruct);
473
            $urlAliasNames = $this->nameSchemaService->resolveUrlAliasSchema($content);
474
            foreach ($urlAliasNames as $languageCode => $name) {
475
                $this->persistenceHandler->urlAliasHandler()->publishUrlAliasForLocation(
476
                    $newLocation->id,
477
                    $newLocation->parentId,
478
                    $name,
479
                    $languageCode,
480
                    $contentInfo->alwaysAvailable,
481
                    // @todo: this is legacy storage specific for updating ezcontentobject_tree.path_identification_string, to be removed
482
                    $languageCode === $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 $contentInfo->mainLanguageCode (string) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
483
                );
484
            }
485
486
            $this->repository->commit();
487
        } catch (Exception $e) {
488
            $this->repository->rollback();
489
            throw $e;
490
        }
491
492
        return $this->domainMapper->buildLocationWithContent($newLocation, $content);
493
    }
494
495
    /**
496
     * Updates $location in the content repository.
497
     *
498
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed to update this location
499
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException   if if set the remoteId exists already
500
     *
501
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
502
     * @param \eZ\Publish\API\Repository\Values\Content\LocationUpdateStruct $locationUpdateStruct
503
     *
504
     * @return \eZ\Publish\API\Repository\Values\Content\Location the updated Location
505
     */
506
    public function updateLocation(APILocation $location, LocationUpdateStruct $locationUpdateStruct)
507
    {
508
        if (!$this->domainMapper->isValidLocationPriority($locationUpdateStruct->priority)) {
509
            throw new InvalidArgumentValue('priority', $locationUpdateStruct->priority, 'LocationUpdateStruct');
510
        }
511
512 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...
513
            throw new InvalidArgumentValue('remoteId', $locationUpdateStruct->remoteId, 'LocationUpdateStruct');
514
        }
515
516
        if ($locationUpdateStruct->sortField !== null && !$this->domainMapper->isValidLocationSortField($locationUpdateStruct->sortField)) {
517
            throw new InvalidArgumentValue('sortField', $locationUpdateStruct->sortField, 'LocationUpdateStruct');
518
        }
519
520
        if ($locationUpdateStruct->sortOrder !== null && !$this->domainMapper->isValidLocationSortOrder($locationUpdateStruct->sortOrder)) {
521
            throw new InvalidArgumentValue('sortOrder', $locationUpdateStruct->sortOrder, 'LocationUpdateStruct');
522
        }
523
524
        $loadedLocation = $this->loadLocation($location->id);
525
526
        if ($locationUpdateStruct->remoteId !== null) {
527
            try {
528
                $existingLocation = $this->loadLocationByRemoteId($locationUpdateStruct->remoteId);
529
                if ($existingLocation !== null && $existingLocation->id !== $loadedLocation->id) {
530
                    throw new InvalidArgumentException('locationUpdateStruct', 'location with provided remote ID already exists');
531
                }
532
            } catch (APINotFoundException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
533
            }
534
        }
535
536
        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...
537
            throw new UnauthorizedException('content', 'edit', ['locationId' => $loadedLocation->id]);
538
        }
539
540
        $updateStruct = new UpdateStruct();
541
        $updateStruct->priority = $locationUpdateStruct->priority !== null ? $locationUpdateStruct->priority : $loadedLocation->priority;
542
        $updateStruct->remoteId = $locationUpdateStruct->remoteId !== null ? trim($locationUpdateStruct->remoteId) : $loadedLocation->remoteId;
543
        $updateStruct->sortField = $locationUpdateStruct->sortField !== null ? $locationUpdateStruct->sortField : $loadedLocation->sortField;
544
        $updateStruct->sortOrder = $locationUpdateStruct->sortOrder !== null ? $locationUpdateStruct->sortOrder : $loadedLocation->sortOrder;
545
546
        $this->repository->beginTransaction();
547
        try {
548
            $this->persistenceHandler->locationHandler()->update($updateStruct, $loadedLocation->id);
549
            $this->repository->commit();
550
        } catch (Exception $e) {
551
            $this->repository->rollback();
552
            throw $e;
553
        }
554
555
        return $this->loadLocation($loadedLocation->id);
556
    }
557
558
    /**
559
     * Swaps the contents held by $location1 and $location2.
560
     *
561
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed to swap content
562
     *
563
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location1
564
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location2
565
     */
566
    public function swapLocation(APILocation $location1, APILocation $location2)
567
    {
568
        $loadedLocation1 = $this->loadLocation($location1->id);
569
        $loadedLocation2 = $this->loadLocation($location2->id);
570
571
        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...
572
            throw new UnauthorizedException('content', 'edit', ['locationId' => $loadedLocation1->id]);
573
        }
574
        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...
575
            throw new UnauthorizedException('content', 'edit', ['locationId' => $loadedLocation2->id]);
576
        }
577
578
        $this->repository->beginTransaction();
579
        try {
580
            $this->persistenceHandler->locationHandler()->swap($loadedLocation1->id, $loadedLocation2->id);
581
            $this->persistenceHandler->urlAliasHandler()->locationSwapped(
582
                $location1->id,
583
                $location1->parentLocationId,
584
                $location2->id,
585
                $location2->parentLocationId
586
            );
587
            $this->persistenceHandler->bookmarkHandler()->locationSwapped($loadedLocation1->id, $loadedLocation2->id);
588
            $this->repository->commit();
589
        } catch (Exception $e) {
590
            $this->repository->rollback();
591
            throw $e;
592
        }
593
    }
594
595
    /**
596
     * Hides the $location and marks invisible all descendants of $location.
597
     *
598
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed to hide this location
599
     *
600
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
601
     *
602
     * @return \eZ\Publish\API\Repository\Values\Content\Location $location, with updated hidden value
603
     */
604 View Code Duplication
    public function hideLocation(APILocation $location)
605
    {
606
        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...
607
            throw new UnauthorizedException('content', 'hide', ['locationId' => $location->id]);
608
        }
609
610
        $this->repository->beginTransaction();
611
        try {
612
            $this->persistenceHandler->locationHandler()->hide($location->id);
613
            $this->repository->commit();
614
        } catch (Exception $e) {
615
            $this->repository->rollback();
616
            throw $e;
617
        }
618
619
        return $this->loadLocation($location->id);
620
    }
621
622
    /**
623
     * Unhides the $location.
624
     *
625
     * This method and marks visible all descendants of $locations
626
     * until a hidden location is found.
627
     *
628
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed to unhide this location
629
     *
630
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
631
     *
632
     * @return \eZ\Publish\API\Repository\Values\Content\Location $location, with updated hidden value
633
     */
634 View Code Duplication
    public function unhideLocation(APILocation $location)
635
    {
636
        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...
637
            throw new UnauthorizedException('content', 'hide', ['locationId' => $location->id]);
638
        }
639
640
        $this->repository->beginTransaction();
641
        try {
642
            $this->persistenceHandler->locationHandler()->unHide($location->id);
643
            $this->repository->commit();
644
        } catch (Exception $e) {
645
            $this->repository->rollback();
646
            throw $e;
647
        }
648
649
        return $this->loadLocation($location->id);
650
    }
651
652
    /**
653
     * Moves the subtree to $newParentLocation.
654
     *
655
     * If a user has the permission to move the location to a target location
656
     * he can do it regardless of an existing descendant on which the user has no permission.
657
     *
658
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed to move this location to the target
659
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user does not have read access to the whole source subtree
660
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException If the new parent is in a subtree of the location
661
     *
662
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
663
     * @param \eZ\Publish\API\Repository\Values\Content\Location $newParentLocation
664
     */
665
    public function moveSubtree(APILocation $location, APILocation $newParentLocation)
666
    {
667
        $location = $this->loadLocation($location->id);
668
        $newParentLocation = $this->loadLocation($newParentLocation->id);
669
670
        // check create permission on target location
671
        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...
672
            throw new UnauthorizedException('content', 'create', ['locationId' => $newParentLocation->id]);
673
        }
674
675
        /** Check read access to whole source subtree
676
         * @var bool|\eZ\Publish\API\Repository\Values\Content\Query\Criterion
677
         */
678
        $contentReadCriterion = $this->permissionCriterionResolver->getPermissionsCriterion();
0 ignored issues
show
Bug introduced by
The call to getPermissionsCriterion() misses some required arguments starting with $module.
Loading history...
679 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...
680
            throw new UnauthorizedException('content', 'read');
681
        } elseif ($contentReadCriterion !== true) {
682
            // Query if there are any content in subtree current user don't have access to
683
            $query = new Query(
684
                [
685
                    'limit' => 0,
686
                    'filter' => new CriterionLogicalAnd(
687
                        [
688
                            new CriterionSubtree($location->pathString),
689
                            new CriterionLogicalNot($contentReadCriterion),
0 ignored issues
show
Bug introduced by
It seems like $contentReadCriterion defined by $this->permissionCriteri...tPermissionsCriterion() on line 678 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...
690
                        ]
691
                    ),
692
                ]
693
            );
694
            $result = $this->repository->getSearchService()->findContent($query, [], false);
695
            if ($result->totalCount > 0) {
696
                throw new UnauthorizedException('content', 'read');
697
            }
698
        }
699
700
        if (strpos($newParentLocation->pathString, $location->pathString) === 0) {
701
            throw new InvalidArgumentException(
702
                '$newParentLocation',
703
                'new parent location is in a subtree of the given $location'
704
            );
705
        }
706
707
        $this->repository->beginTransaction();
708
        try {
709
            $this->persistenceHandler->locationHandler()->move($location->id, $newParentLocation->id);
710
711
            $content = $this->repository->getContentService()->loadContent($location->contentId);
712
            $urlAliasNames = $this->nameSchemaService->resolveUrlAliasSchema($content);
713 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...
714
                $this->persistenceHandler->urlAliasHandler()->publishUrlAliasForLocation(
715
                    $location->id,
716
                    $newParentLocation->id,
717
                    $name,
718
                    $languageCode,
719
                    $content->contentInfo->alwaysAvailable
720
                );
721
            }
722
723
            $this->persistenceHandler->urlAliasHandler()->locationMoved(
724
                $location->id,
725
                $location->parentLocationId,
726
                $newParentLocation->id
727
            );
728
729
            $this->repository->commit();
730
        } catch (Exception $e) {
731
            $this->repository->rollback();
732
            throw $e;
733
        }
734
    }
735
736
    /**
737
     * Deletes $location and all its descendants.
738
     *
739
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user is not allowed to delete this location or a descendant
740
     *
741
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
742
     */
743
    public function deleteLocation(APILocation $location)
744
    {
745
        $location = $this->loadLocation($location->id);
746
747
        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...
748
            throw new UnauthorizedException('content', 'manage_locations', ['locationId' => $location->id]);
749
        }
750
        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...
751
            throw new UnauthorizedException('content', 'remove', ['locationId' => $location->id]);
752
        }
753
754
        /** Check remove access to descendants
755
         * @var bool|\eZ\Publish\API\Repository\Values\Content\Query\Criterion
756
         */
757
        $contentReadCriterion = $this->permissionCriterionResolver->getPermissionsCriterion('content', 'remove');
758 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...
759
            throw new UnauthorizedException('content', 'remove');
760
        } elseif ($contentReadCriterion !== true) {
761
            // Query if there are any content in subtree current user don't have access to
762
            $query = new Query(
763
                [
764
                    'limit' => 0,
765
                    'filter' => new CriterionLogicalAnd(
766
                        [
767
                            new CriterionSubtree($location->pathString),
768
                            new CriterionLogicalNot($contentReadCriterion),
0 ignored issues
show
Bug introduced by
It seems like $contentReadCriterion defined by $this->permissionCriteri...on('content', 'remove') on line 757 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...
769
                        ]
770
                    ),
771
                ]
772
            );
773
            $result = $this->repository->getSearchService()->findContent($query, [], false);
774
            if ($result->totalCount > 0) {
775
                throw new UnauthorizedException('content', 'remove');
776
            }
777
        }
778
779
        $this->repository->beginTransaction();
780
        try {
781
            $this->persistenceHandler->locationHandler()->removeSubtree($location->id);
782
            $this->persistenceHandler->urlAliasHandler()->locationDeleted($location->id);
783
            $this->repository->commit();
784
        } catch (Exception $e) {
785
            $this->repository->rollback();
786
            throw $e;
787
        }
788
    }
789
790
    /**
791
     * Instantiates a new location create class.
792
     *
793
     * @param mixed $parentLocationId the parent under which the new location should be created
794
     * @param eZ\Publish\API\Repository\Values\ContentType\ContentType|null $contentType
795
     *
796
     * @return \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct
797
     */
798 View Code Duplication
    public function newLocationCreateStruct($parentLocationId, ContentType $contentType = null)
799
    {
800
        $properties = [
801
            'parentLocationId' => $parentLocationId,
802
        ];
803
        if ($contentType) {
804
            $properties['sortField'] = $contentType->defaultSortField;
805
            $properties['sortOrder'] = $contentType->defaultSortOrder;
806
        }
807
808
        return new LocationCreateStruct($properties);
809
    }
810
811
    /**
812
     * Instantiates a new location update class.
813
     *
814
     * @return \eZ\Publish\API\Repository\Values\Content\LocationUpdateStruct
815
     */
816
    public function newLocationUpdateStruct()
817
    {
818
        return new LocationUpdateStruct();
819
    }
820
821
    /**
822
     * Get the total number of all existing Locations. Can be combined with loadAllLocations.
823
     *
824
     * @see loadAllLocations
825
     *
826
     * @return int Total number of Locations
827
     */
828
    public function getAllLocationsCount(): int
829
    {
830
        return $this->persistenceHandler->locationHandler()->countAllLocations();
831
    }
832
833
    /**
834
     * Bulk-load all existing Locations, constrained by $limit and $offset to paginate results.
835
     *
836
     * @param int $offset
837
     * @param int $limit
838
     *
839
     * @return \eZ\Publish\API\Repository\Values\Content\Location[]
840
     *
841
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
842
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
843
     */
844
    public function loadAllLocations(int $offset = 0, int $limit = 25): array
845
    {
846
        $spiLocations = $this->persistenceHandler->locationHandler()->loadAllLocations(
847
            $offset,
848
            $limit
849
        );
850
        $contentIds = array_unique(
851
            array_map(
852
                function (SPILocation $spiLocation) {
853
                    return $spiLocation->contentId;
854
                },
855
                $spiLocations
856
            )
857
        );
858
859
        $permissionResolver = $this->repository->getPermissionResolver();
860
        $spiContentInfoList = $this->persistenceHandler->contentHandler()->loadContentInfoList(
861
            $contentIds
862
        );
863
        $contentList = $this->domainMapper->buildContentProxyList(
864
            $spiContentInfoList,
865
            Language::ALL,
866
            false
867
        );
868
        $locations = [];
869
        foreach ($spiLocations as $spiLocation) {
870
            if (!isset($spiContentInfoList[$spiLocation->contentId], $contentList[$spiLocation->contentId])) {
871
                $this->logger->warning(
872
                    sprintf(
873
                        'Location %d has missing Content %d',
874
                        $spiLocation->id,
875
                        $spiLocation->contentId
876
                    )
877
                );
878
                continue;
879
            }
880
881
            $location = $this->domainMapper->buildLocationWithContent(
882
                $spiLocation,
883
                $contentList[$spiLocation->contentId],
884
                $spiContentInfoList[$spiLocation->contentId]
885
            );
886
887
            $contentInfo = $location->getContentInfo();
888
            if (!$permissionResolver->canUser('content', 'read', $contentInfo, [$location])) {
889
                continue;
890
            }
891
            $locations[] = $location;
892
        }
893
894
        return $locations;
895
    }
896
}
897