Completed
Push — EZP-29539 ( 66a3f4 )
by
unknown
21:13
created

TrashService::findTrashItems()   C

Complexity

Conditions 15
Paths 9

Size

Total Lines 41

Duplication

Lines 6
Ratio 14.63 %

Importance

Changes 0
Metric Value
cc 15
nc 9
nop 1
dl 6
loc 41
rs 5.9166
c 0
b 0
f 0

How to fix   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\TrashService 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\TrashService as TrashServiceInterface;
12
use eZ\Publish\API\Repository\Repository as RepositoryInterface;
13
use eZ\Publish\API\Repository\Values\Content\Content;
14
use eZ\Publish\API\Repository\Exceptions\UnauthorizedException as APIUnauthorizedException;
15
use eZ\Publish\Core\REST\Client\Values\Content\ContentInfo;
16
use eZ\Publish\API\Repository\Values\Content\ContentInfo as APIContentInfo;
17
use eZ\Publish\SPI\Persistence\Handler;
18
use eZ\Publish\API\Repository\Values\Content\Location;
19
use eZ\Publish\Core\Repository\Values\Content\TrashItem;
20
use eZ\Publish\API\Repository\Values\Content\TrashItem as APITrashItem;
21
use eZ\Publish\API\Repository\Values\Content\Query;
22
use eZ\Publish\SPI\Persistence\Content\Location\Trashed;
23
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentValue;
24
use eZ\Publish\Core\Base\Exceptions\UnauthorizedException;
25
use eZ\Publish\API\Repository\Values\Content\Trash\SearchResult;
26
use eZ\Publish\API\Repository\Values\Content\Query\Criterion;
27
use eZ\Publish\API\Repository\Values\Content\Query\SortClause;
28
use eZ\Publish\API\Repository\PermissionCriterionResolver;
29
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\LogicalAnd as CriterionLogicalAnd;
30
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\LogicalNot as CriterionLogicalNot;
31
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\Subtree as CriterionSubtree;
32
use DateTime;
33
use Exception;
34
35
/**
36
 * Trash service, used for managing trashed content.
37
 */
38
class TrashService implements TrashServiceInterface
39
{
40
    /**
41
     * @var \eZ\Publish\Core\Repository\Repository
42
     */
43
    protected $repository;
44
45
    /**
46
     * @var \eZ\Publish\SPI\Persistence\Handler
47
     */
48
    protected $persistenceHandler;
49
50
    /**
51
     * @var array
52
     */
53
    protected $settings;
54
55
    /**
56
     * @var \eZ\Publish\Core\Repository\Helper\NameSchemaService
57
     */
58
    protected $nameSchemaService;
59
60
    /**
61
     * Setups service with reference to repository object that created it & corresponding handler.
62
     *
63
     * @param \eZ\Publish\API\Repository\Repository $repository
64
     * @param \eZ\Publish\SPI\Persistence\Handler $handler
65
     * @param \eZ\Publish\Core\Repository\Helper\NameSchemaService $nameSchemaService
66
     * @param array $settings
67
     */
68
    public function __construct(
69
        RepositoryInterface $repository,
70
        Handler $handler,
71
        Helper\NameSchemaService $nameSchemaService,
72
        array $settings = array(),
73
        PermissionCriterionResolver $permissionCriterionResolver
74
    ) {
75
        $this->permissionCriterionResolver = $permissionCriterionResolver;
0 ignored issues
show
Bug introduced by
The property permissionCriterionResolver does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
76
        $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...
77
        $this->persistenceHandler = $handler;
78
        $this->nameSchemaService = $nameSchemaService;
79
        // Union makes sure default settings are ignored if provided in argument
80
        $this->settings = $settings + array(//'defaultSetting' => array(),
81
            );
82
    }
83
84
    /**
85
     * Loads a trashed location object from its $id.
86
     *
87
     * Note that $id is identical to original location, which has been previously trashed
88
     *
89
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read the trashed location
90
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the location with the given id does not exist
91
     *
92
     * @param mixed $trashItemId
93
     *
94
     * @return \eZ\Publish\API\Repository\Values\Content\TrashItem
95
     */
96
    public function loadTrashItem($trashItemId)
97
    {
98
        $spiTrashItem = $this->persistenceHandler->trashHandler()->loadTrashItem($trashItemId);
99
        $trash = $this->buildDomainTrashItemObject(
100
            $spiTrashItem,
101
            $this->repository->getContentService()->internalLoadContent($spiTrashItem->contentId)
0 ignored issues
show
Bug introduced by
The method internalLoadContent() does not exist on eZ\Publish\API\Repository\ContentService. Did you maybe mean loadContent()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
102
        );
103
        if (!$this->repository->canUser('content', 'read', $trash->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...
104
            throw new UnauthorizedException('content', 'read');
105
        }
106
107
        if (!$this->repository->canUser('content', 'restore', $trash->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...
108
            throw new UnauthorizedException('content', 'restore');
109
        }
110
111
        return $trash;
112
    }
113
114
    /**
115
     * Sends $location and all its children to trash and returns the corresponding trash item.
116
     *
117
     * The current user may not have access to the returned trash item, check before using it.
118
     * Content is left untouched.
119
     *
120
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to trash the given location
121
     *
122
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
123
     *
124
     * @return null|\eZ\Publish\API\Repository\Values\Content\TrashItem null if location was deleted, otherwise TrashItem
125
     */
126
    public function trash(Location $location)
127
    {
128
        if (!is_numeric($location->id)) {
129
            throw new InvalidArgumentValue('id', $location->id, 'Location');
130
        }
131
132
        if (!$this->userHasPermissionsToRemove($location->getContentInfo(), $location)) {
133
            throw new UnauthorizedException('content', 'remove');
134
        }
135
136
        $this->repository->beginTransaction();
137
        try {
138
            $spiTrashItem = $this->persistenceHandler->trashHandler()->trashSubtree($location->id);
139
            $this->persistenceHandler->urlAliasHandler()->locationDeleted($location->id);
140
            $this->repository->commit();
141
        } catch (Exception $e) {
142
            $this->repository->rollback();
143
            throw $e;
144
        }
145
146
        // Use internalLoadContent() as we want a trash item regardless of user access to the trash or not.
147
        try {
148
            return isset($spiTrashItem)
149
                ? $this->buildDomainTrashItemObject(
150
                    $spiTrashItem,
151
                    $this->repository->getContentService()->internalLoadContent($spiTrashItem->contentId)
0 ignored issues
show
Bug introduced by
The method internalLoadContent() does not exist on eZ\Publish\API\Repository\ContentService. Did you maybe mean loadContent()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
152
                )
153
                : null;
154
        } catch (Exception $e) {
155
            return null;
156
        }
157
    }
158
159
    /**
160
     * Recovers the $trashedLocation at its original place if possible.
161
     *
162
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to recover the trash item at the parent location location
163
     *
164
     * If $newParentLocation is provided, $trashedLocation will be restored under it.
165
     *
166
     * @param \eZ\Publish\API\Repository\Values\Content\TrashItem $trashItem
167
     * @param \eZ\Publish\API\Repository\Values\Content\Location $newParentLocation
168
     *
169
     * @return \eZ\Publish\API\Repository\Values\Content\Location the newly created or recovered location
170
     */
171
    public function recover(APITrashItem $trashItem, Location $newParentLocation = null)
172
    {
173
        if (!is_numeric($trashItem->id)) {
174
            throw new InvalidArgumentValue('id', $trashItem->id, 'TrashItem');
175
        }
176
177
        if ($newParentLocation === null && !is_numeric($trashItem->parentLocationId)) {
178
            throw new InvalidArgumentValue('parentLocationId', $trashItem->parentLocationId, 'TrashItem');
179
        }
180
181
        if ($newParentLocation !== null && !is_numeric($newParentLocation->id)) {
182
            throw new InvalidArgumentValue('parentLocationId', $newParentLocation->id, 'Location');
183
        }
184
185
        if (!$this->repository->canUser(
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...
186
            'content',
187
            'restore',
188
            $trashItem->getContentInfo(),
189
            [$newParentLocation ?: $trashItem]
190
        )) {
191
            throw new UnauthorizedException('content', 'restore');
192
        }
193
194
        $this->repository->beginTransaction();
195
        try {
196
            $newParentLocationId = $newParentLocation ? $newParentLocation->id : $trashItem->parentLocationId;
197
            $newLocationId = $this->persistenceHandler->trashHandler()->recover(
198
                $trashItem->id,
199
                $newParentLocationId
200
            );
201
202
            $content = $this->repository->getContentService()->loadContent($trashItem->contentId);
203
            $urlAliasNames = $this->nameSchemaService->resolveUrlAliasSchema($content);
204
205
            // Publish URL aliases for recovered location
206
            foreach ($urlAliasNames as $languageCode => $name) {
207
                $this->persistenceHandler->urlAliasHandler()->publishUrlAliasForLocation(
208
                    $newLocationId,
209
                    $newParentLocationId,
210
                    $name,
211
                    $languageCode,
212
                    $content->contentInfo->alwaysAvailable
213
                );
214
            }
215
216
            $this->repository->commit();
217
        } catch (Exception $e) {
218
            $this->repository->rollback();
219
            throw $e;
220
        }
221
222
        return $this->repository->getLocationService()->loadLocation($newLocationId);
223
    }
224
225
    /**
226
     * Empties trash.
227
     *
228
     * All locations contained in the trash will be removed. Content objects will be removed
229
     * if all locations of the content are gone.
230
     *
231
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to empty the trash
232
     */
233 View Code Duplication
    public function emptyTrash()
234
    {
235
        // Will throw if you have Role assignment limitation where you have content/cleantrash permission.
236
        // This is by design and means you can only delete one and one trash item, or you'll need to change how
237
        // permissions is assigned to be on separate role with no Role assignment limitation.
238
        if ($this->repository->hasAccess('content', 'cleantrash') !== true) {
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\Core\Repository\Repository::hasAccess() has been deprecated with message: since 6.6, to be removed. Use PermissionResolver::hasAccess() instead. Check if user has access to a given module / function. Low level function, use canUser instead if you have objects to check against.

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...
239
            throw new UnauthorizedException('content', 'cleantrash');
240
        }
241
242
        $this->repository->beginTransaction();
243
        try {
244
            // Persistence layer takes care of deleting content objects
245
            $this->persistenceHandler->trashHandler()->emptyTrash();
246
            $this->repository->commit();
247
        } catch (Exception $e) {
248
            $this->repository->rollback();
249
            throw $e;
250
        }
251
    }
252
253
    /**
254
     * Deletes a trash item.
255
     *
256
     * The corresponding content object will be removed
257
     *
258
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to delete this trash item
259
     *
260
     * @param \eZ\Publish\API\Repository\Values\Content\TrashItem $trashItem
261
     */
262
    public function deleteTrashItem(APITrashItem $trashItem)
263
    {
264
        if (!$this->repository->canUser('content', 'cleantrash', $trashItem->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...
265
            throw new UnauthorizedException('content', 'cleantrash');
266
        }
267
268
        if (!is_numeric($trashItem->id)) {
269
            throw new InvalidArgumentValue('id', $trashItem->id, 'TrashItem');
270
        }
271
272
        $this->repository->beginTransaction();
273
        try {
274
            $this->persistenceHandler->trashHandler()->deleteTrashItem($trashItem->id);
275
            $this->repository->commit();
276
        } catch (Exception $e) {
277
            $this->repository->rollback();
278
            throw $e;
279
        }
280
    }
281
282
    /**
283
     * Returns a collection of Trashed locations contained in the trash, which are readable by the current user.
284
     *
285
     * $query allows to filter/sort the elements to be contained in the collection.
286
     *
287
     * @param \eZ\Publish\API\Repository\Values\Content\Query $query
288
     *
289
     * @return \eZ\Publish\API\Repository\Values\Content\Trash\SearchResult
290
     */
291
    public function findTrashItems(Query $query)
292
    {
293
        if ($query->filter !== null && !$query->filter instanceof Criterion) {
294
            throw new InvalidArgumentValue('query->filter', $query->filter, 'Query');
295
        }
296
297
        if ($query->sortClauses !== null) {
298
            if (!is_array($query->sortClauses)) {
299
                throw new InvalidArgumentValue('query->sortClauses', $query->sortClauses, 'Query');
300
            }
301
302
            foreach ($query->sortClauses as $sortClause) {
303
                if (!$sortClause instanceof SortClause) {
304
                    throw new InvalidArgumentValue('query->sortClauses',
305
                        'only instances of SortClause class are allowed');
306
                }
307
            }
308
        }
309
310 View Code Duplication
        if ($query->offset !== null && !is_numeric($query->offset)) {
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...
311
            throw new InvalidArgumentValue('query->offset', $query->offset, 'Query');
312
        }
313
314 View Code Duplication
        if ($query->limit !== null && !is_numeric($query->limit)) {
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...
315
            throw new InvalidArgumentValue('query->limit', $query->limit, 'Query');
316
        }
317
318
        $spiTrashItems = $this->persistenceHandler->trashHandler()->findTrashItems(
319
            $query->filter,
320
            $query->offset !== null && $query->offset > 0 ? (int)$query->offset : 0,
321
            $query->limit !== null && $query->limit >= 1 ? (int)$query->limit : null,
322
            $query->sortClauses
323
        );
324
325
        $trashItems = $this->buildDomainTrashItems($spiTrashItems);
326
        $searchResult = new SearchResult();
327
        $searchResult->totalCount = $searchResult->count = count($trashItems);
328
        $searchResult->items = $trashItems;
329
330
        return $searchResult;
331
    }
332
333
    protected function buildDomainTrashItems(array $spiTrashItems): array
334
    {
335
        $trashItems = array();
336
        // TODO: load content in bulk once API allows for it
337
        foreach ($spiTrashItems as $spiTrashItem) {
338
            try {
339
                $trashItems[] = $this->buildDomainTrashItemObject(
340
                    $spiTrashItem,
341
                    $this->repository->getContentService()->loadContent($spiTrashItem->contentId)
342
                );
343
            } catch (APIUnauthorizedException $e) {
344
                // Do nothing, thus exclude items the current user doesn't have read access to.
345
            }
346
        }
347
348
        return $trashItems;
349
    }
350
351 View Code Duplication
    protected function buildDomainTrashItemObject(Trashed $spiTrashItem, Content $content): APITrashItem
352
    {
353
        return new TrashItem(
354
            array(
355
                'content' => $content,
356
                'contentInfo' => $content->contentInfo,
357
                'id' => $spiTrashItem->id,
358
                'priority' => $spiTrashItem->priority,
359
                'hidden' => $spiTrashItem->hidden,
360
                'invisible' => $spiTrashItem->invisible,
361
                'remoteId' => $spiTrashItem->remoteId,
362
                'parentLocationId' => $spiTrashItem->parentId,
363
                'pathString' => $spiTrashItem->pathString,
364
                'depth' => $spiTrashItem->depth,
365
                'sortField' => $spiTrashItem->sortField,
366
                'sortOrder' => $spiTrashItem->sortOrder,
367
            )
368
        );
369
    }
370
371
    /**
372
     * @param int $timestamp
373
     *
374
     * @return \DateTime
375
     */
376
    protected function getDateTime($timestamp)
377
    {
378
        $dateTime = new DateTime();
379
        $dateTime->setTimestamp($timestamp);
380
381
        return $dateTime;
382
    }
383
384
    /**
385
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
386
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
387
     *
388
     * @return bool
389
     *
390
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
391
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
392
     */
393
    private function userHasPermissionsToRemove(APIContentInfo $contentInfo, Location $location): bool
394
    {
395
        if (!$this->repository->canUser('content', 'remove', $contentInfo, [$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...
396
            return false;
397
        }
398
399
        $contentRemoveCriterion = $this->permissionCriterionResolver->getPermissionsCriterion('content', 'remove');
400
401
        if ($contentRemoveCriterion === false) {
402
            return false;
403
        } elseif ($contentRemoveCriterion !== true) {
404
            // Query if there are any content in subtree current user don't have access to
405
            $query = new Query(
406
                array(
407
                    'limit' => 0,
408
                    'filter' => new CriterionLogicalAnd(
409
                        array(
410
                            new CriterionSubtree($location->pathString),
411
                            new CriterionLogicalNot($contentRemoveCriterion),
412
                        )
413
                    ),
414
                )
415
            );
416
417
            $result = $this->repository->getSearchService()->findContent($query, array(), false);
418
            if ($result->totalCount > 0) {
419
                return false;
420
            }
421
422
            return true;
423
        }
424
    }
425
}
426