Completed
Push — EZP-29539 ( 63f372...d578e8 )
by
unknown
20:26
created

TrashService::userHasPermissionsToRemove()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 2
dl 0
loc 24
rs 9.536
c 0
b 0
f 0
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\SPI\Persistence\Handler;
14
use eZ\Publish\API\Repository\Values\Content\Location;
15
use eZ\Publish\Core\Repository\Values\Content\TrashItem;
16
use eZ\Publish\API\Repository\Values\Content\TrashItem as APITrashItem;
17
use eZ\Publish\API\Repository\Values\Content\ContentInfo;
18
use eZ\Publish\API\Repository\Values\Content\Query;
19
use eZ\Publish\SPI\Persistence\Content\Location\Trashed;
20
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentValue;
21
use eZ\Publish\Core\Base\Exceptions\UnauthorizedException;
22
use eZ\Publish\API\Repository\Values\Content\SearchResult;
23
use eZ\Publish\API\Repository\Values\Content\Query\Criterion;
24
use eZ\Publish\API\Repository\Values\Content\Query\SortClause;
25
use eZ\Publish\API\Repository\PermissionCriterionResolver;
26
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\LogicalAnd as CriterionLogicalAnd;
27
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\LogicalNot as CriterionLogicalNot;
28
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\Subtree as CriterionSubtree;
29
use DateTime;
30
use Exception;
31
32
/**
33
 * Trash service, used for managing trashed content.
34
 */
35
class TrashService implements TrashServiceInterface
36
{
37
    /**
38
     * @var \eZ\Publish\Core\Repository\Repository
39
     */
40
    protected $repository;
41
42
    /**
43
     * @var \eZ\Publish\SPI\Persistence\Handler
44
     */
45
    protected $persistenceHandler;
46
47
    /**
48
     * @var array
49
     */
50
    protected $settings;
51
52
    /**
53
     * @var \eZ\Publish\Core\Repository\Helper\NameSchemaService
54
     */
55
    protected $nameSchemaService;
56
57
    /**
58
     * Setups service with reference to repository object that created it & corresponding handler.
59
     *
60
     * @param \eZ\Publish\API\Repository\Repository $repository
61
     * @param \eZ\Publish\SPI\Persistence\Handler $handler
62
     * @param \eZ\Publish\Core\Repository\Helper\NameSchemaService $nameSchemaService
63
     * @param \eZ\Publish\API\Repository\PermissionCriterionResolver $permissionCriterionResolver
64
     * @param array $settings
65
     */
66
    public function __construct(
67
        RepositoryInterface $repository,
68
        Handler $handler,
69
        Helper\NameSchemaService $nameSchemaService,
70
        PermissionCriterionResolver $permissionCriterionResolver,
71
        array $settings = array()
72
    ) {
73
        $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...
74
        $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...
75
        $this->persistenceHandler = $handler;
76
        $this->nameSchemaService = $nameSchemaService;
77
        // Union makes sure default settings are ignored if provided in argument
78
        $this->settings = $settings + array(
79
            //'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($spiTrashItem);
99
        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...
100
            throw new UnauthorizedException('content', 'read');
101
        }
102
103
        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...
104
            throw new UnauthorizedException('content', 'restore');
105
        }
106
107
        return $trash;
108
    }
109
110
    /**
111
     * Sends $location and all its children to trash and returns the corresponding trash item.
112
     *
113
     * The current user may not have access to the returned trash item, check before using it.
114
     * Content is left untouched.
115
     *
116
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to trash the given location
117
     *
118
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
119
     *
120
     * @return null|\eZ\Publish\API\Repository\Values\Content\TrashItem null if location was deleted, otherwise TrashItem
121
     */
122
    public function trash(Location $location)
123
    {
124
        if (empty($location->id)) {
125
            throw new InvalidArgumentValue('id', $location->id, 'Location');
126
        }
127
128
        if (!$this->userHasPermissionsToRemove($location->getContentInfo(), $location)) {
129
            throw new UnauthorizedException('content', 'remove');
130
        }
131
132
        $this->repository->beginTransaction();
133
        try {
134
            $spiTrashItem = $this->persistenceHandler->trashHandler()->trashSubtree($location->id);
135
            $this->persistenceHandler->urlAliasHandler()->locationDeleted($location->id);
136
            $this->repository->commit();
137
        } catch (Exception $e) {
138
            $this->repository->rollback();
139
            throw $e;
140
        }
141
142
        // Use sudo as we want a trash item regardless of user access to the trash.
143
        try {
144
            return isset($spiTrashItem)
145
                ? $this->repository->sudo(
146
                    function () use ($spiTrashItem) {
147
                        return $this->buildDomainTrashItemObject($spiTrashItem);
148
                    }
149
                )
150
                : null;
151
        } catch (Exception $e) {
152
            return null;
153
        }
154
    }
155
156
    /**
157
     * Recovers the $trashedLocation at its original place if possible.
158
     *
159
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to recover the trash item at the parent location location
160
     *
161
     * If $newParentLocation is provided, $trashedLocation will be restored under it.
162
     *
163
     * @param \eZ\Publish\API\Repository\Values\Content\TrashItem $trashItem
164
     * @param \eZ\Publish\API\Repository\Values\Content\Location $newParentLocation
165
     *
166
     * @return \eZ\Publish\API\Repository\Values\Content\Location the newly created or recovered location
167
     */
168
    public function recover(APITrashItem $trashItem, Location $newParentLocation = null)
169
    {
170
        if (!is_numeric($trashItem->id)) {
171
            throw new InvalidArgumentValue('id', $trashItem->id, 'TrashItem');
172
        }
173
174
        if ($newParentLocation === null && !is_numeric($trashItem->parentLocationId)) {
175
            throw new InvalidArgumentValue('parentLocationId', $trashItem->parentLocationId, 'TrashItem');
176
        }
177
178
        if ($newParentLocation !== null && !is_numeric($newParentLocation->id)) {
179
            throw new InvalidArgumentValue('parentLocationId', $newParentLocation->id, 'Location');
180
        }
181
182
        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...
183
            'content',
184
            'restore',
185
            $trashItem->getContentInfo(),
186
            [$newParentLocation ?: $trashItem]
187
        )) {
188
            throw new UnauthorizedException('content', 'restore');
189
        }
190
191
        $this->repository->beginTransaction();
192
        try {
193
            $newParentLocationId = $newParentLocation ? $newParentLocation->id : $trashItem->parentLocationId;
194
            $newLocationId = $this->persistenceHandler->trashHandler()->recover(
195
                $trashItem->id,
196
                $newParentLocationId
197
            );
198
199
            $content = $this->repository->getContentService()->loadContent($trashItem->contentId);
200
            $urlAliasNames = $this->nameSchemaService->resolveUrlAliasSchema($content);
201
202
            // Publish URL aliases for recovered location
203
            foreach ($urlAliasNames as $languageCode => $name) {
204
                $this->persistenceHandler->urlAliasHandler()->publishUrlAliasForLocation(
205
                    $newLocationId,
206
                    $newParentLocationId,
207
                    $name,
208
                    $languageCode,
209
                    $content->contentInfo->alwaysAvailable
210
                );
211
            }
212
213
            $this->repository->commit();
214
        } catch (Exception $e) {
215
            $this->repository->rollback();
216
            throw $e;
217
        }
218
219
        return $this->repository->getLocationService()->loadLocation($newLocationId);
220
    }
221
222
    /**
223
     * Empties trash.
224
     *
225
     * All locations contained in the trash will be removed. Content objects will be removed
226
     * if all locations of the content are gone.
227
     *
228
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to empty the trash
229
     */
230 View Code Duplication
    public function emptyTrash()
231
    {
232
        // Will throw if you have Role assignment limitation where you have content/cleantrash permission.
233
        // This is by design and means you can only delete one and one trash item, or you'll need to change how
234
        // permissions is assigned to be on separate role with no Role assignment limitation.
235
        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...
236
            throw new UnauthorizedException('content', 'cleantrash');
237
        }
238
239
        $this->repository->beginTransaction();
240
        try {
241
            // Persistence layer takes care of deleting content objects
242
            $this->persistenceHandler->trashHandler()->emptyTrash();
243
            $this->repository->commit();
244
        } catch (Exception $e) {
245
            $this->repository->rollback();
246
            throw $e;
247
        }
248
    }
249
250
    /**
251
     * Deletes a trash item.
252
     *
253
     * The corresponding content object will be removed
254
     *
255
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to delete this trash item
256
     *
257
     * @param \eZ\Publish\API\Repository\Values\Content\TrashItem $trashItem
258
     */
259
    public function deleteTrashItem(APITrashItem $trashItem)
260
    {
261
        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...
262
            throw new UnauthorizedException('content', 'cleantrash');
263
        }
264
265
        if (!is_numeric($trashItem->id)) {
266
            throw new InvalidArgumentValue('id', $trashItem->id, 'TrashItem');
267
        }
268
269
        $this->repository->beginTransaction();
270
        try {
271
            $this->persistenceHandler->trashHandler()->deleteTrashItem($trashItem->id);
272
            $this->repository->commit();
273
        } catch (Exception $e) {
274
            $this->repository->rollback();
275
            throw $e;
276
        }
277
    }
278
279
    /**
280
     * Returns a collection of Trashed locations contained in the trash, which are readable by the current user.
281
     *
282
     * $query allows to filter/sort the elements to be contained in the collection.
283
     *
284
     * @param \eZ\Publish\API\Repository\Values\Content\Query $query
285
     *
286
     * @return \eZ\Publish\API\Repository\Values\Content\SearchResult
287
     */
288
    public function findTrashItems(Query $query)
289
    {
290
        if ($query->filter !== null && !$query->filter instanceof Criterion) {
291
            throw new InvalidArgumentValue('query->filter', $query->filter, 'Query');
292
        }
293
294
        if ($query->sortClauses !== null) {
295
            if (!is_array($query->sortClauses)) {
296
                throw new InvalidArgumentValue('query->sortClauses', $query->sortClauses, 'Query');
297
            }
298
299
            foreach ($query->sortClauses as $sortClause) {
300
                if (!$sortClause instanceof SortClause) {
301
                    throw new InvalidArgumentValue('query->sortClauses', 'only instances of SortClause class are allowed');
302
                }
303
            }
304
        }
305
306
        if ($query->offset !== null && !is_numeric($query->offset)) {
307
            throw new InvalidArgumentValue('query->offset', $query->offset, 'Query');
308
        }
309
310
        if ($query->limit !== null && !is_numeric($query->limit)) {
311
            throw new InvalidArgumentValue('query->limit', $query->limit, 'Query');
312
        }
313
314
        $spiTrashItems = $this->persistenceHandler->trashHandler()->findTrashItems(
315
            $query->filter !== null ? $query->filter : null,
316
            $query->offset !== null && $query->offset > 0 ? (int)$query->offset : 0,
317
            $query->limit !== null && $query->limit >= 1 ? (int)$query->limit : null,
318
            $query->sortClauses !== null ? $query->sortClauses : null
0 ignored issues
show
Bug introduced by
It seems like $query->sortClauses !== ...ery->sortClauses : null can also be of type array; however, eZ\Publish\SPI\Persisten...ndler::findTrashItems() does only seem to accept null|array<integer,objec...tent\Query\SortClause>>, 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...
319
        );
320
321
        $trashItems = array();
322
        foreach ($spiTrashItems as $spiTrashItem) {
323
            try {
324
                $trashItems[] = $this->buildDomainTrashItemObject($spiTrashItem);
325
            } catch (UnauthorizedException $e) {
326
                // Do nothing, thus exclude items the current user doesn't have read access to.
327
            }
328
        }
329
330
        $searchResult = new SearchResult();
0 ignored issues
show
Deprecated Code introduced by
The class eZ\Publish\API\Repositor...es\Content\SearchResult has been deprecated with message: This class is returned by find methods providing a result of a search.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
331
        $searchResult->count = count($trashItems);
332
        $searchResult->items = $trashItems;
333
        $searchResult->query = $query;
334
335
        return $searchResult;
336
    }
337
338
    /**
339
     * Builds the domain TrashItem object from provided persistence trash item.
340
     *
341
     * @param \eZ\Publish\SPI\Persistence\Content\Location\Trashed $spiTrashItem
342
     *
343
     * @return \eZ\Publish\API\Repository\Values\Content\TrashItem
344
     */
345 View Code Duplication
    protected function buildDomainTrashItemObject(Trashed $spiTrashItem)
346
    {
347
        return new TrashItem(
348
            array(
349
                'contentInfo' => $this->repository->getContentService()->loadContentInfo($spiTrashItem->contentId),
350
                'id' => $spiTrashItem->id,
351
                'priority' => $spiTrashItem->priority,
352
                'hidden' => $spiTrashItem->hidden,
353
                'invisible' => $spiTrashItem->invisible,
354
                'remoteId' => $spiTrashItem->remoteId,
355
                'parentLocationId' => $spiTrashItem->parentId,
356
                'pathString' => $spiTrashItem->pathString,
357
                'depth' => $spiTrashItem->depth,
358
                'sortField' => $spiTrashItem->sortField,
359
                'sortOrder' => $spiTrashItem->sortOrder,
360
            )
361
        );
362
    }
363
364
    /**
365
     * @param int $timestamp
366
     *
367
     * @return \DateTime
368
     */
369
    protected function getDateTime($timestamp)
370
    {
371
        $dateTime = new DateTime();
372
        $dateTime->setTimestamp($timestamp);
373
374
        return $dateTime;
375
    }
376
377
    /**
378
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
379
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
380
     *
381
     * @return bool
382
     *
383
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
384
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
385
     */
386
    private function userHasPermissionsToRemove(ContentInfo $contentInfo, Location $location): bool
387
    {
388
        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...
389
            return false;
390
        }
391
        $contentRemoveCriterion = $this->permissionCriterionResolver->getPermissionsCriterion('content', 'remove');
392
        if (!$contentRemoveCriterion instanceof Criterion) {
393
            return (bool)$contentRemoveCriterion;
394
        }
395
        $query = new Query(
396
            array(
397
                'limit' => 0,
398
                'filter' => new CriterionLogicalAnd(
399
                    array(
400
                        new CriterionSubtree($location->pathString),
401
                        new CriterionLogicalNot($contentRemoveCriterion),
402
                    )
403
                ),
404
            )
405
        );
406
        $result = $this->repository->getSearchService()->findContent($query, array(), false);
407
408
        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...
409
    }
410
}
411