Completed
Push — EZP-31461 ( 32c2c8...078940 )
by
unknown
20:42
created

LocationService::createLocation()   C

Complexity

Conditions 11
Paths 21

Size

Total Lines 84

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
nc 21
nop 2
dl 0
loc 84
rs 6.2024
c 0
b 0
f 0

How to fix   Long Method    Complexity   

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('content', 'read');
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...rion('content', 'read') 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
        $locationCreateStruct->sortField = $locationCreateStruct->sortField ?? $content->getContentType()->defaultSortField;
432
        $locationCreateStruct->sortOrder = $locationCreateStruct->sortOrder ?? $content->getContentType()->defaultSortOrder;
433
434
        if (empty($locationCreateStruct->sortField) || empty($locationCreateStruct->sortOrder)) {
435
            throw new InvalidArgumentException(
436
                '$locationCreateStruct',
437
                'Sorting parameters are not set.'
438
            );
439
        }
440
441
        $contentInfo = $content->contentInfo;
442
443
        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...
444
            throw new UnauthorizedException('content', 'manage_locations', ['contentId' => $contentInfo->id]);
445
        }
446
447
        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...
448
            throw new UnauthorizedException('content', 'create', ['locationId' => $parentLocation->id]);
449
        }
450
451
        // Check if the parent is a sub location of one of the existing content locations (this also solves the
452
        // situation where parent location actually one of the content locations),
453
        // or if the content already has location below given location create struct parent
454
        $existingContentLocations = $this->loadLocations($contentInfo);
455
        if (!empty($existingContentLocations)) {
456
            foreach ($existingContentLocations as $existingContentLocation) {
457
                if (stripos($parentLocation->pathString, $existingContentLocation->pathString) !== false) {
458
                    throw new InvalidArgumentException(
459
                        '$locationCreateStruct',
460
                        'Specified parent is a sub location of one of the existing content locations.'
461
                    );
462
                }
463
                if ($parentLocation->id == $existingContentLocation->parentLocationId) {
464
                    throw new InvalidArgumentException(
465
                        '$locationCreateStruct',
466
                        'Content is already below the specified parent.'
467
                    );
468
                }
469
            }
470
        }
471
472
        $spiLocationCreateStruct = $this->domainMapper->buildSPILocationCreateStruct(
473
            $locationCreateStruct,
474
            $parentLocation,
475
            $contentInfo->mainLocationId ?? true,
476
            $contentInfo->id,
477
            $contentInfo->currentVersionNo
478
        );
479
480
        $this->repository->beginTransaction();
481
        try {
482
            $newLocation = $this->persistenceHandler->locationHandler()->create($spiLocationCreateStruct);
483
            $urlAliasNames = $this->nameSchemaService->resolveUrlAliasSchema($content);
484
            foreach ($urlAliasNames as $languageCode => $name) {
485
                $this->persistenceHandler->urlAliasHandler()->publishUrlAliasForLocation(
486
                    $newLocation->id,
487
                    $newLocation->parentId,
488
                    $name,
489
                    $languageCode,
490
                    $contentInfo->alwaysAvailable,
491
                    // @todo: this is legacy storage specific for updating ezcontentobject_tree.path_identification_string, to be removed
492
                    $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...
493
                );
494
            }
495
496
            $this->repository->commit();
497
        } catch (Exception $e) {
498
            $this->repository->rollback();
499
            throw $e;
500
        }
501
502
        return $this->domainMapper->buildLocationWithContent($newLocation, $content);
503
    }
504
505
    /**
506
     * Updates $location in the content repository.
507
     *
508
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed to update this location
509
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException   if if set the remoteId exists already
510
     *
511
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
512
     * @param \eZ\Publish\API\Repository\Values\Content\LocationUpdateStruct $locationUpdateStruct
513
     *
514
     * @return \eZ\Publish\API\Repository\Values\Content\Location the updated Location
515
     */
516
    public function updateLocation(APILocation $location, LocationUpdateStruct $locationUpdateStruct)
517
    {
518
        if (!$this->domainMapper->isValidLocationPriority($locationUpdateStruct->priority)) {
519
            throw new InvalidArgumentValue('priority', $locationUpdateStruct->priority, 'LocationUpdateStruct');
520
        }
521
522
        if ($locationUpdateStruct->remoteId !== null && (!is_string($locationUpdateStruct->remoteId) || empty($locationUpdateStruct->remoteId))) {
523
            throw new InvalidArgumentValue('remoteId', $locationUpdateStruct->remoteId, 'LocationUpdateStruct');
524
        }
525
526
        if ($locationUpdateStruct->sortField !== null && !$this->domainMapper->isValidLocationSortField($locationUpdateStruct->sortField)) {
527
            throw new InvalidArgumentValue('sortField', $locationUpdateStruct->sortField, 'LocationUpdateStruct');
528
        }
529
530
        if ($locationUpdateStruct->sortOrder !== null && !$this->domainMapper->isValidLocationSortOrder($locationUpdateStruct->sortOrder)) {
531
            throw new InvalidArgumentValue('sortOrder', $locationUpdateStruct->sortOrder, 'LocationUpdateStruct');
532
        }
533
534
        $loadedLocation = $this->loadLocation($location->id);
535
536
        if ($locationUpdateStruct->remoteId !== null) {
537
            try {
538
                $existingLocation = $this->loadLocationByRemoteId($locationUpdateStruct->remoteId);
539
                if ($existingLocation !== null && $existingLocation->id !== $loadedLocation->id) {
540
                    throw new InvalidArgumentException('locationUpdateStruct', 'location with provided remote ID already exists');
541
                }
542
            } catch (APINotFoundException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
543
            }
544
        }
545
546
        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...
547
            throw new UnauthorizedException('content', 'edit', ['locationId' => $loadedLocation->id]);
548
        }
549
550
        $updateStruct = new UpdateStruct();
551
        $updateStruct->priority = $locationUpdateStruct->priority !== null ? $locationUpdateStruct->priority : $loadedLocation->priority;
552
        $updateStruct->remoteId = $locationUpdateStruct->remoteId !== null ? trim($locationUpdateStruct->remoteId) : $loadedLocation->remoteId;
553
        $updateStruct->sortField = $locationUpdateStruct->sortField !== null ? $locationUpdateStruct->sortField : $loadedLocation->sortField;
554
        $updateStruct->sortOrder = $locationUpdateStruct->sortOrder !== null ? $locationUpdateStruct->sortOrder : $loadedLocation->sortOrder;
555
556
        $this->repository->beginTransaction();
557
        try {
558
            $this->persistenceHandler->locationHandler()->update($updateStruct, $loadedLocation->id);
559
            $this->repository->commit();
560
        } catch (Exception $e) {
561
            $this->repository->rollback();
562
            throw $e;
563
        }
564
565
        return $this->loadLocation($loadedLocation->id);
566
    }
567
568
    /**
569
     * Swaps the contents held by $location1 and $location2.
570
     *
571
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed to swap content
572
     *
573
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location1
574
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location2
575
     */
576
    public function swapLocation(APILocation $location1, APILocation $location2)
577
    {
578
        $loadedLocation1 = $this->loadLocation($location1->id);
579
        $loadedLocation2 = $this->loadLocation($location2->id);
580
581
        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...
582
            throw new UnauthorizedException('content', 'edit', ['locationId' => $loadedLocation1->id]);
583
        }
584
        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...
585
            throw new UnauthorizedException('content', 'edit', ['locationId' => $loadedLocation2->id]);
586
        }
587
588
        $this->repository->beginTransaction();
589
        try {
590
            $this->persistenceHandler->locationHandler()->swap($loadedLocation1->id, $loadedLocation2->id);
591
            $this->persistenceHandler->urlAliasHandler()->locationSwapped(
592
                $location1->id,
593
                $location1->parentLocationId,
594
                $location2->id,
595
                $location2->parentLocationId
596
            );
597
            $this->persistenceHandler->bookmarkHandler()->locationSwapped($loadedLocation1->id, $loadedLocation2->id);
598
            $this->repository->commit();
599
        } catch (Exception $e) {
600
            $this->repository->rollback();
601
            throw $e;
602
        }
603
    }
604
605
    /**
606
     * Hides the $location and marks invisible all descendants of $location.
607
     *
608
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed to hide this location
609
     *
610
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
611
     *
612
     * @return \eZ\Publish\API\Repository\Values\Content\Location $location, with updated hidden value
613
     */
614 View Code Duplication
    public function hideLocation(APILocation $location)
615
    {
616
        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...
617
            throw new UnauthorizedException('content', 'hide', ['locationId' => $location->id]);
618
        }
619
620
        $this->repository->beginTransaction();
621
        try {
622
            $this->persistenceHandler->locationHandler()->hide($location->id);
623
            $this->repository->commit();
624
        } catch (Exception $e) {
625
            $this->repository->rollback();
626
            throw $e;
627
        }
628
629
        return $this->loadLocation($location->id);
630
    }
631
632
    /**
633
     * Unhides the $location.
634
     *
635
     * This method and marks visible all descendants of $locations
636
     * until a hidden location is found.
637
     *
638
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed to unhide this location
639
     *
640
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
641
     *
642
     * @return \eZ\Publish\API\Repository\Values\Content\Location $location, with updated hidden value
643
     */
644 View Code Duplication
    public function unhideLocation(APILocation $location)
645
    {
646
        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...
647
            throw new UnauthorizedException('content', 'hide', ['locationId' => $location->id]);
648
        }
649
650
        $this->repository->beginTransaction();
651
        try {
652
            $this->persistenceHandler->locationHandler()->unHide($location->id);
653
            $this->repository->commit();
654
        } catch (Exception $e) {
655
            $this->repository->rollback();
656
            throw $e;
657
        }
658
659
        return $this->loadLocation($location->id);
660
    }
661
662
    /**
663
     * Moves the subtree to $newParentLocation.
664
     *
665
     * If a user has the permission to move the location to a target location
666
     * he can do it regardless of an existing descendant on which the user has no permission.
667
     *
668
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed to move this location to the target
669
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user does not have read access to the whole source subtree
670
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException If the new parent is in a subtree of the location
671
     *
672
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
673
     * @param \eZ\Publish\API\Repository\Values\Content\Location $newParentLocation
674
     */
675
    public function moveSubtree(APILocation $location, APILocation $newParentLocation)
676
    {
677
        $location = $this->loadLocation($location->id);
678
        $newParentLocation = $this->loadLocation($newParentLocation->id);
679
680
        // check create permission on target location
681
        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...
682
            throw new UnauthorizedException('content', 'create', ['locationId' => $newParentLocation->id]);
683
        }
684
685
        /** Check read access to whole source subtree
686
         * @var bool|\eZ\Publish\API\Repository\Values\Content\Query\Criterion
687
         */
688
        $contentReadCriterion = $this->permissionCriterionResolver->getPermissionsCriterion('content', 'read');
689 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...
690
            throw new UnauthorizedException('content', 'read');
691
        } elseif ($contentReadCriterion !== true) {
692
            // Query if there are any content in subtree current user don't have access to
693
            $query = new Query(
694
                [
695
                    'limit' => 0,
696
                    'filter' => new CriterionLogicalAnd(
697
                        [
698
                            new CriterionSubtree($location->pathString),
699
                            new CriterionLogicalNot($contentReadCriterion),
0 ignored issues
show
Bug introduced by
It seems like $contentReadCriterion defined by $this->permissionCriteri...rion('content', 'read') on line 688 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...
700
                        ]
701
                    ),
702
                ]
703
            );
704
            $result = $this->repository->getSearchService()->findContent($query, [], false);
705
            if ($result->totalCount > 0) {
706
                throw new UnauthorizedException('content', 'read');
707
            }
708
        }
709
710
        if (strpos($newParentLocation->pathString, $location->pathString) === 0) {
711
            throw new InvalidArgumentException(
712
                '$newParentLocation',
713
                'new parent location is in a subtree of the given $location'
714
            );
715
        }
716
717
        $this->repository->beginTransaction();
718
        try {
719
            $this->persistenceHandler->locationHandler()->move($location->id, $newParentLocation->id);
720
721
            $content = $this->repository->getContentService()->loadContent($location->contentId);
722
            $urlAliasNames = $this->nameSchemaService->resolveUrlAliasSchema($content);
723 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...
724
                $this->persistenceHandler->urlAliasHandler()->publishUrlAliasForLocation(
725
                    $location->id,
726
                    $newParentLocation->id,
727
                    $name,
728
                    $languageCode,
729
                    $content->contentInfo->alwaysAvailable
730
                );
731
            }
732
733
            $this->persistenceHandler->urlAliasHandler()->locationMoved(
734
                $location->id,
735
                $location->parentLocationId,
736
                $newParentLocation->id
737
            );
738
739
            $this->repository->commit();
740
        } catch (Exception $e) {
741
            $this->repository->rollback();
742
            throw $e;
743
        }
744
    }
745
746
    /**
747
     * Deletes $location and all its descendants.
748
     *
749
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user is not allowed to delete this location or a descendant
750
     *
751
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
752
     */
753
    public function deleteLocation(APILocation $location)
754
    {
755
        $location = $this->loadLocation($location->id);
756
757
        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...
758
            throw new UnauthorizedException('content', 'manage_locations', ['locationId' => $location->id]);
759
        }
760
        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...
761
            throw new UnauthorizedException('content', 'remove', ['locationId' => $location->id]);
762
        }
763
764
        /** Check remove access to descendants
765
         * @var bool|\eZ\Publish\API\Repository\Values\Content\Query\Criterion
766
         */
767
        $contentReadCriterion = $this->permissionCriterionResolver->getPermissionsCriterion('content', 'remove');
768 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...
769
            throw new UnauthorizedException('content', 'remove');
770
        } elseif ($contentReadCriterion !== true) {
771
            // Query if there are any content in subtree current user don't have access to
772
            $query = new Query(
773
                [
774
                    'limit' => 0,
775
                    'filter' => new CriterionLogicalAnd(
776
                        [
777
                            new CriterionSubtree($location->pathString),
778
                            new CriterionLogicalNot($contentReadCriterion),
0 ignored issues
show
Bug introduced by
It seems like $contentReadCriterion defined by $this->permissionCriteri...on('content', 'remove') on line 767 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...
779
                        ]
780
                    ),
781
                ]
782
            );
783
            $result = $this->repository->getSearchService()->findContent($query, [], false);
784
            if ($result->totalCount > 0) {
785
                throw new UnauthorizedException('content', 'remove');
786
            }
787
        }
788
789
        $this->repository->beginTransaction();
790
        try {
791
            $this->persistenceHandler->locationHandler()->removeSubtree($location->id);
792
            $this->persistenceHandler->urlAliasHandler()->locationDeleted($location->id);
793
            $this->repository->commit();
794
        } catch (Exception $e) {
795
            $this->repository->rollback();
796
            throw $e;
797
        }
798
    }
799
800
    /**
801
     * Instantiates a new location create class.
802
     *
803
     * @param mixed $parentLocationId the parent under which the new location should be created
804
     * @param eZ\Publish\API\Repository\Values\ContentType\ContentType|null $contentType
805
     *
806
     * @return \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct
807
     */
808 View Code Duplication
    public function newLocationCreateStruct($parentLocationId, ContentType $contentType = null)
809
    {
810
        $properties = [
811
            'parentLocationId' => $parentLocationId,
812
        ];
813
        if ($contentType) {
814
            $properties['sortField'] = $contentType->defaultSortField;
815
            $properties['sortOrder'] = $contentType->defaultSortOrder;
816
        }
817
818
        return new LocationCreateStruct($properties);
819
    }
820
821
    /**
822
     * Instantiates a new location update class.
823
     *
824
     * @return \eZ\Publish\API\Repository\Values\Content\LocationUpdateStruct
825
     */
826
    public function newLocationUpdateStruct()
827
    {
828
        return new LocationUpdateStruct();
829
    }
830
831
    /**
832
     * Get the total number of all existing Locations. Can be combined with loadAllLocations.
833
     *
834
     * @see loadAllLocations
835
     *
836
     * @return int Total number of Locations
837
     */
838
    public function getAllLocationsCount(): int
839
    {
840
        return $this->persistenceHandler->locationHandler()->countAllLocations();
841
    }
842
843
    /**
844
     * Bulk-load all existing Locations, constrained by $limit and $offset to paginate results.
845
     *
846
     * @param int $offset
847
     * @param int $limit
848
     *
849
     * @return \eZ\Publish\API\Repository\Values\Content\Location[]
850
     *
851
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
852
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
853
     */
854
    public function loadAllLocations(int $offset = 0, int $limit = 25): array
855
    {
856
        $spiLocations = $this->persistenceHandler->locationHandler()->loadAllLocations(
857
            $offset,
858
            $limit
859
        );
860
        $contentIds = array_unique(
861
            array_map(
862
                function (SPILocation $spiLocation) {
863
                    return $spiLocation->contentId;
864
                },
865
                $spiLocations
866
            )
867
        );
868
869
        $permissionResolver = $this->repository->getPermissionResolver();
870
        $spiContentInfoList = $this->persistenceHandler->contentHandler()->loadContentInfoList(
871
            $contentIds
872
        );
873
        $contentList = $this->domainMapper->buildContentProxyList(
874
            $spiContentInfoList,
875
            Language::ALL,
876
            false
877
        );
878
        $locations = [];
879
        foreach ($spiLocations as $spiLocation) {
880
            if (!isset($spiContentInfoList[$spiLocation->contentId], $contentList[$spiLocation->contentId])) {
881
                $this->logger->warning(
882
                    sprintf(
883
                        'Location %d has missing Content %d',
884
                        $spiLocation->id,
885
                        $spiLocation->contentId
886
                    )
887
                );
888
                continue;
889
            }
890
891
            $location = $this->domainMapper->buildLocationWithContent(
892
                $spiLocation,
893
                $contentList[$spiLocation->contentId],
894
                $spiContentInfoList[$spiLocation->contentId]
895
            );
896
897
            $contentInfo = $location->getContentInfo();
898
            if (!$permissionResolver->canUser('content', 'read', $contentInfo, [$location])) {
899
                continue;
900
            }
901
            $locations[] = $location;
902
        }
903
904
        return $locations;
905
    }
906
}
907