Completed
Push — apply-code-style ( 1a43bf...37cc85 )
by
unknown
45:48
created

LocationService::newLocationCreateStruct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12

Duplication

Lines 12
Ratio 100 %

Importance

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