Completed
Push — 7.5 ( f0f45d...1f9f67 )
by Łukasz
16:16
created

LocationService::createLocation()   C

Complexity

Conditions 9
Paths 20

Size

Total Lines 81

Duplication

Lines 0
Ratio 0 %

Importance

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