Completed
Push — EZP-29539 ( 66a3f4...0dbe8d )
by
unknown
22:03
created

TrashService   B

Complexity

Total Complexity 51

Size/Duplication

Total Lines 384
Duplicated Lines 11.46 %

Coupling/Cohesion

Components 1
Dependencies 22

Importance

Changes 0
Metric Value
dl 44
loc 384
rs 7.92
c 0
b 0
f 0
wmc 51
lcom 1
cbo 22

11 Methods

Rating   Name   Duplication   Size   Complexity  
A getDateTime() 0 7 1
A __construct() 0 15 1
A loadTrashItem() 0 17 3
B trash() 0 32 6
C recover() 0 53 11
A emptyTrash() 19 19 3
A deleteTrashItem() 0 19 4
C findTrashItems() 6 41 15
A buildDomainTrashItems() 0 17 3
A buildDomainTrashItemObject() 19 19 1
A userHasPermissionsToRemove() 0 28 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like TrashService often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use TrashService, and based on these observations, apply Extract Interface, too.

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\API\Repository\Values\Content\ContentInfo;
16
use eZ\Publish\SPI\Persistence\Handler;
17
use eZ\Publish\API\Repository\Values\Content\Location;
18
use eZ\Publish\Core\Repository\Values\Content\TrashItem;
19
use eZ\Publish\API\Repository\Values\Content\TrashItem as APITrashItem;
20
use eZ\Publish\API\Repository\Values\Content\Query;
21
use eZ\Publish\SPI\Persistence\Content\Location\Trashed;
22
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentValue;
23
use eZ\Publish\Core\Base\Exceptions\UnauthorizedException;
24
use eZ\Publish\API\Repository\Values\Content\Trash\SearchResult;
25
use eZ\Publish\API\Repository\Values\Content\Query\Criterion;
26
use eZ\Publish\API\Repository\Values\Content\Query\SortClause;
27
use eZ\Publish\API\Repository\PermissionCriterionResolver;
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 DateTime;
32
use Exception;
33
34
/**
35
 * Trash service, used for managing trashed content.
36
 */
37
class TrashService implements TrashServiceInterface
38
{
39
    /**
40
     * @var \eZ\Publish\Core\Repository\Repository
41
     */
42
    protected $repository;
43
44
    /**
45
     * @var \eZ\Publish\SPI\Persistence\Handler
46
     */
47
    protected $persistenceHandler;
48
49
    /**
50
     * @var array
51
     */
52
    protected $settings;
53
54
    /**
55
     * @var \eZ\Publish\Core\Repository\Helper\NameSchemaService
56
     */
57
    protected $nameSchemaService;
58
59
    /**
60
     * Setups service with reference to repository object that created it & corresponding handler.
61
     *
62
     * @param \eZ\Publish\API\Repository\Repository $repository
63
     * @param \eZ\Publish\SPI\Persistence\Handler $handler
64
     * @param \eZ\Publish\Core\Repository\Helper\NameSchemaService $nameSchemaService
65
     * @param array $settings
66
     */
67
    public function __construct(
68
        RepositoryInterface $repository,
69
        Handler $handler,
70
        Helper\NameSchemaService $nameSchemaService,
71
        array $settings = array(),
72
        PermissionCriterionResolver $permissionCriterionResolver
73
    ) {
74
        $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...
75
        $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...
76
        $this->persistenceHandler = $handler;
77
        $this->nameSchemaService = $nameSchemaService;
78
        // Union makes sure default settings are ignored if provided in argument
79
        $this->settings = $settings + array(//'defaultSetting' => array(),
80
            );
81
    }
82
83
    /**
84
     * Loads a trashed location object from its $id.
85
     *
86
     * Note that $id is identical to original location, which has been previously trashed
87
     *
88
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to read the trashed location
89
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException - if the location with the given id does not exist
90
     *
91
     * @param mixed $trashItemId
92
     *
93
     * @return \eZ\Publish\API\Repository\Values\Content\TrashItem
94
     */
95
    public function loadTrashItem($trashItemId)
96
    {
97
        $spiTrashItem = $this->persistenceHandler->trashHandler()->loadTrashItem($trashItemId);
98
        $trash = $this->buildDomainTrashItemObject(
99
            $spiTrashItem,
100
            $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...
101
        );
102
        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...
103
            throw new UnauthorizedException('content', 'read');
104
        }
105
106
        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...
107
            throw new UnauthorizedException('content', 'restore');
108
        }
109
110
        return $trash;
111
    }
112
113
    /**
114
     * Sends $location and all its children to trash and returns the corresponding trash item.
115
     *
116
     * The current user may not have access to the returned trash item, check before using it.
117
     * Content is left untouched.
118
     *
119
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to trash the given location
120
     *
121
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
122
     *
123
     * @return null|\eZ\Publish\API\Repository\Values\Content\TrashItem null if location was deleted, otherwise TrashItem
124
     */
125
    public function trash(Location $location)
126
    {
127
        if (!is_numeric($location->id)) {
128
            throw new InvalidArgumentValue('id', $location->id, 'Location');
129
        }
130
131
        if (!$this->userHasPermissionsToRemove($location->getContentInfo(), $location)) {
132
            throw new UnauthorizedException('content', 'remove');
133
        }
134
135
        $this->repository->beginTransaction();
136
        try {
137
            $spiTrashItem = $this->persistenceHandler->trashHandler()->trashSubtree($location->id);
138
            $this->persistenceHandler->urlAliasHandler()->locationDeleted($location->id);
139
            $this->repository->commit();
140
        } catch (Exception $e) {
141
            $this->repository->rollback();
142
            throw $e;
143
        }
144
145
        // Use internalLoadContent() as we want a trash item regardless of user access to the trash or not.
146
        try {
147
            return isset($spiTrashItem)
148
                ? $this->buildDomainTrashItemObject(
149
                    $spiTrashItem,
150
                    $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...
151
                )
152
                : null;
153
        } catch (Exception $e) {
154
            return null;
155
        }
156
    }
157
158
    /**
159
     * Recovers the $trashedLocation at its original place if possible.
160
     *
161
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to recover the trash item at the parent location location
162
     *
163
     * If $newParentLocation is provided, $trashedLocation will be restored under it.
164
     *
165
     * @param \eZ\Publish\API\Repository\Values\Content\TrashItem $trashItem
166
     * @param \eZ\Publish\API\Repository\Values\Content\Location $newParentLocation
167
     *
168
     * @return \eZ\Publish\API\Repository\Values\Content\Location the newly created or recovered location
169
     */
170
    public function recover(APITrashItem $trashItem, Location $newParentLocation = null)
171
    {
172
        if (!is_numeric($trashItem->id)) {
173
            throw new InvalidArgumentValue('id', $trashItem->id, 'TrashItem');
174
        }
175
176
        if ($newParentLocation === null && !is_numeric($trashItem->parentLocationId)) {
177
            throw new InvalidArgumentValue('parentLocationId', $trashItem->parentLocationId, 'TrashItem');
178
        }
179
180
        if ($newParentLocation !== null && !is_numeric($newParentLocation->id)) {
181
            throw new InvalidArgumentValue('parentLocationId', $newParentLocation->id, 'Location');
182
        }
183
184
        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...
185
            'content',
186
            'restore',
187
            $trashItem->getContentInfo(),
188
            [$newParentLocation ?: $trashItem]
189
        )) {
190
            throw new UnauthorizedException('content', 'restore');
191
        }
192
193
        $this->repository->beginTransaction();
194
        try {
195
            $newParentLocationId = $newParentLocation ? $newParentLocation->id : $trashItem->parentLocationId;
196
            $newLocationId = $this->persistenceHandler->trashHandler()->recover(
197
                $trashItem->id,
198
                $newParentLocationId
199
            );
200
201
            $content = $this->repository->getContentService()->loadContent($trashItem->contentId);
202
            $urlAliasNames = $this->nameSchemaService->resolveUrlAliasSchema($content);
203
204
            // Publish URL aliases for recovered location
205
            foreach ($urlAliasNames as $languageCode => $name) {
206
                $this->persistenceHandler->urlAliasHandler()->publishUrlAliasForLocation(
207
                    $newLocationId,
208
                    $newParentLocationId,
209
                    $name,
210
                    $languageCode,
211
                    $content->contentInfo->alwaysAvailable
212
                );
213
            }
214
215
            $this->repository->commit();
216
        } catch (Exception $e) {
217
            $this->repository->rollback();
218
            throw $e;
219
        }
220
221
        return $this->repository->getLocationService()->loadLocation($newLocationId);
222
    }
223
224
    /**
225
     * Empties trash.
226
     *
227
     * All locations contained in the trash will be removed. Content objects will be removed
228
     * if all locations of the content are gone.
229
     *
230
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to empty the trash
231
     */
232 View Code Duplication
    public function emptyTrash()
233
    {
234
        // Will throw if you have Role assignment limitation where you have content/cleantrash permission.
235
        // This is by design and means you can only delete one and one trash item, or you'll need to change how
236
        // permissions is assigned to be on separate role with no Role assignment limitation.
237
        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...
238
            throw new UnauthorizedException('content', 'cleantrash');
239
        }
240
241
        $this->repository->beginTransaction();
242
        try {
243
            // Persistence layer takes care of deleting content objects
244
            $this->persistenceHandler->trashHandler()->emptyTrash();
245
            $this->repository->commit();
246
        } catch (Exception $e) {
247
            $this->repository->rollback();
248
            throw $e;
249
        }
250
    }
251
252
    /**
253
     * Deletes a trash item.
254
     *
255
     * The corresponding content object will be removed
256
     *
257
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to delete this trash item
258
     *
259
     * @param \eZ\Publish\API\Repository\Values\Content\TrashItem $trashItem
260
     */
261
    public function deleteTrashItem(APITrashItem $trashItem)
262
    {
263
        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...
264
            throw new UnauthorizedException('content', 'cleantrash');
265
        }
266
267
        if (!is_numeric($trashItem->id)) {
268
            throw new InvalidArgumentValue('id', $trashItem->id, 'TrashItem');
269
        }
270
271
        $this->repository->beginTransaction();
272
        try {
273
            $this->persistenceHandler->trashHandler()->deleteTrashItem($trashItem->id);
274
            $this->repository->commit();
275
        } catch (Exception $e) {
276
            $this->repository->rollback();
277
            throw $e;
278
        }
279
    }
280
281
    /**
282
     * Returns a collection of Trashed locations contained in the trash, which are readable by the current user.
283
     *
284
     * $query allows to filter/sort the elements to be contained in the collection.
285
     *
286
     * @param \eZ\Publish\API\Repository\Values\Content\Query $query
287
     *
288
     * @return \eZ\Publish\API\Repository\Values\Content\Trash\SearchResult
289
     */
290
    public function findTrashItems(Query $query)
291
    {
292
        if ($query->filter !== null && !$query->filter instanceof Criterion) {
293
            throw new InvalidArgumentValue('query->filter', $query->filter, 'Query');
294
        }
295
296
        if ($query->sortClauses !== null) {
297
            if (!is_array($query->sortClauses)) {
298
                throw new InvalidArgumentValue('query->sortClauses', $query->sortClauses, 'Query');
299
            }
300
301
            foreach ($query->sortClauses as $sortClause) {
302
                if (!$sortClause instanceof SortClause) {
303
                    throw new InvalidArgumentValue('query->sortClauses',
304
                        'only instances of SortClause class are allowed');
305
                }
306
            }
307
        }
308
309 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...
310
            throw new InvalidArgumentValue('query->offset', $query->offset, 'Query');
311
        }
312
313 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...
314
            throw new InvalidArgumentValue('query->limit', $query->limit, 'Query');
315
        }
316
317
        $spiTrashItems = $this->persistenceHandler->trashHandler()->findTrashItems(
318
            $query->filter,
319
            $query->offset !== null && $query->offset > 0 ? (int)$query->offset : 0,
320
            $query->limit !== null && $query->limit >= 1 ? (int)$query->limit : null,
321
            $query->sortClauses
322
        );
323
324
        $trashItems = $this->buildDomainTrashItems($spiTrashItems);
325
        $searchResult = new SearchResult();
326
        $searchResult->totalCount = $searchResult->count = count($trashItems);
327
        $searchResult->items = $trashItems;
328
329
        return $searchResult;
330
    }
331
332
    protected function buildDomainTrashItems(array $spiTrashItems): array
333
    {
334
        $trashItems = array();
335
        // TODO: load content in bulk once API allows for it
336
        foreach ($spiTrashItems as $spiTrashItem) {
337
            try {
338
                $trashItems[] = $this->buildDomainTrashItemObject(
339
                    $spiTrashItem,
340
                    $this->repository->getContentService()->loadContent($spiTrashItem->contentId)
341
                );
342
            } catch (APIUnauthorizedException $e) {
343
                // Do nothing, thus exclude items the current user doesn't have read access to.
344
            }
345
        }
346
347
        return $trashItems;
348
    }
349
350 View Code Duplication
    protected function buildDomainTrashItemObject(Trashed $spiTrashItem, Content $content): APITrashItem
351
    {
352
        return new TrashItem(
353
            array(
354
                'content' => $content,
355
                'contentInfo' => $content->contentInfo,
356
                'id' => $spiTrashItem->id,
357
                'priority' => $spiTrashItem->priority,
358
                'hidden' => $spiTrashItem->hidden,
359
                'invisible' => $spiTrashItem->invisible,
360
                'remoteId' => $spiTrashItem->remoteId,
361
                'parentLocationId' => $spiTrashItem->parentId,
362
                'pathString' => $spiTrashItem->pathString,
363
                'depth' => $spiTrashItem->depth,
364
                'sortField' => $spiTrashItem->sortField,
365
                'sortOrder' => $spiTrashItem->sortOrder,
366
            )
367
        );
368
    }
369
370
    /**
371
     * @param int $timestamp
372
     *
373
     * @return \DateTime
374
     */
375
    protected function getDateTime($timestamp)
376
    {
377
        $dateTime = new DateTime();
378
        $dateTime->setTimestamp($timestamp);
379
380
        return $dateTime;
381
    }
382
383
    /**
384
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
385
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
386
     *
387
     * @return bool
388
     *
389
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
390
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
391
     */
392
    private function userHasPermissionsToRemove(ContentInfo $contentInfo, Location $location): bool
393
    {
394
        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...
395
            return false;
396
        }
397
398
        $contentRemoveCriterion = $this->permissionCriterionResolver->getPermissionsCriterion('content', 'remove');
399
400
        if (!$contentRemoveCriterion instanceof Criterion) {
401
            return (bool)$contentRemoveCriterion;
402
        }
403
404
        $query = new Query(
405
            array(
406
                'limit' => 0,
407
                'filter' => new CriterionLogicalAnd(
408
                    array(
409
                        new CriterionSubtree($location->pathString),
410
                        new CriterionLogicalNot($contentRemoveCriterion),
411
                    )
412
                ),
413
            )
414
        );
415
416
        $result = $this->repository->getSearchService()->findContent($query, array(), false);
417
418
        return $result->totalCount == 0;
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $result->totalCount of type integer|null to 0; this is ambiguous as not only 0 == 0 is true, but null == 0 is true, too. Consider using a strict comparison ===.
Loading history...
419
    }
420
}
421