Completed
Push — master ( 9a380f...b40ee9 )
by Łukasz
25:43
created

SectionService::assignSectionToSubtree()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 53

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 53
c 0
b 0
f 0
cc 5
nc 8
nop 2
rs 8.7143

How to fix   Long Method   

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\SectionService 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\Location;
13
use eZ\Publish\API\Repository\Values\Content\Query;
14
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\Subtree as CriterionSubtree;
15
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\LogicalAnd as CriterionLogicalAnd;
16
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\LogicalNot as CriterionLogicalNot;
17
use eZ\Publish\API\Repository\Values\Content\SectionCreateStruct;
18
use eZ\Publish\API\Repository\Values\Content\ContentInfo;
19
use eZ\Publish\API\Repository\Values\Content\Section;
20
use eZ\Publish\API\Repository\Values\Content\SectionUpdateStruct;
21
use eZ\Publish\API\Repository\SectionService as SectionServiceInterface;
22
use eZ\Publish\API\Repository\Repository as RepositoryInterface;
23
use eZ\Publish\SPI\Persistence\Content\Section\Handler as SectionHandler;
24
use eZ\Publish\SPI\Persistence\Content\Location\Handler as LocationHandler;
25
use eZ\Publish\SPI\Persistence\Content\Section as SPISection;
26
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentValue;
27
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException;
28
use eZ\Publish\Core\Base\Exceptions\BadStateException;
29
use eZ\Publish\Core\Base\Exceptions\UnauthorizedException;
30
use eZ\Publish\API\Repository\Exceptions\NotFoundException as APINotFoundException;
31
use Exception;
32
33
/**
34
 * Section service, used for section operations.
35
 */
36
class SectionService implements SectionServiceInterface
37
{
38
    /**
39
     * @var \eZ\Publish\API\Repository\Repository
40
     */
41
    protected $repository;
42
43
    /**
44
     * @var \eZ\Publish\API\Repository\PermissionResolver
45
     */
46
    protected $permissionResolver;
47
48
    /**
49
     * @var \eZ\Publish\API\Repository\PermissionCriterionResolver
50
     */
51
    protected $permissionCriterionResolver;
52
53
    /**
54
     * @var \eZ\Publish\SPI\Persistence\Content\Section\Handler
55
     */
56
    protected $sectionHandler;
57
58
    /**
59
     * @var \eZ\Publish\SPI\Persistence\Content\Location\Handler
60
     */
61
    protected $locationHandler;
62
63
    /**
64
     * @var array
65
     */
66
    protected $settings;
67
68
    /**
69
     * Setups service with reference to repository object that created it & corresponding handler.
70
     *
71
     * @param \eZ\Publish\API\Repository\Repository $repository
72
     * @param \eZ\Publish\SPI\Persistence\Content\Section\Handler $sectionHandler
73
     * @param \eZ\Publish\SPI\Persistence\Content\Location\Handler $locationHandler
74
     * @param \eZ\Publish\API\Repository\PermissionCriterionResolver $permissionCriterionResolver
75
     * @param array $settings
76
     */
77
    public function __construct(RepositoryInterface $repository, SectionHandler $sectionHandler, LocationHandler $locationHandler, PermissionCriterionResolver $permissionCriterionResolver, array $settings = array())
78
    {
79
        $this->repository = $repository;
80
        $this->sectionHandler = $sectionHandler;
81
        $this->locationHandler = $locationHandler;
82
        $this->permissionResolver = $repository->getPermissionResolver();
83
        $this->permissionCriterionResolver = $permissionCriterionResolver;
84
        // Union makes sure default settings are ignored if provided in argument
85
        $this->settings = $settings + array(
86
            //'defaultSetting' => array(),
87
        );
88
    }
89
90
    /**
91
     * Creates a new Section in the content repository.
92
     *
93
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed to create a section
94
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException If the new identifier in $sectionCreateStruct already exists
95
     *
96
     * @param \eZ\Publish\API\Repository\Values\Content\SectionCreateStruct $sectionCreateStruct
97
     *
98
     * @return \eZ\Publish\API\Repository\Values\Content\Section The newly created section
99
     */
100
    public function createSection(SectionCreateStruct $sectionCreateStruct)
101
    {
102 View Code Duplication
        if (!is_string($sectionCreateStruct->name) || empty($sectionCreateStruct->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...
103
            throw new InvalidArgumentValue('name', $sectionCreateStruct->name, 'SectionCreateStruct');
104
        }
105
106 View Code Duplication
        if (!is_string($sectionCreateStruct->identifier) || empty($sectionCreateStruct->identifier)) {
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...
107
            throw new InvalidArgumentValue('identifier', $sectionCreateStruct->identifier, 'SectionCreateStruct');
108
        }
109
110
        if (!$this->permissionResolver->canUser('section', 'edit', $sectionCreateStruct)) {
111
            throw new UnauthorizedException('section', 'edit');
112
        }
113
114
        try {
115
            $existingSection = $this->loadSectionByIdentifier($sectionCreateStruct->identifier);
116
            if ($existingSection !== null) {
117
                throw new InvalidArgumentException('sectionCreateStruct', 'section with specified identifier already exists');
118
            }
119
        } catch (APINotFoundException $e) {
120
            // Do nothing
121
        }
122
123
        $this->repository->beginTransaction();
124
        try {
125
            $spiSection = $this->sectionHandler->create(
126
                $sectionCreateStruct->name,
127
                $sectionCreateStruct->identifier
128
            );
129
            $this->repository->commit();
130
        } catch (Exception $e) {
131
            $this->repository->rollback();
132
            throw $e;
133
        }
134
135
        return $this->buildDomainSectionObject($spiSection);
136
    }
137
138
    /**
139
     * Updates the given section in the content repository.
140
     *
141
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed to create a section
142
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException If the new identifier already exists (if set in the update struct)
143
     *
144
     * @param \eZ\Publish\API\Repository\Values\Content\Section $section
145
     * @param \eZ\Publish\API\Repository\Values\Content\SectionUpdateStruct $sectionUpdateStruct
146
     *
147
     * @return \eZ\Publish\API\Repository\Values\Content\Section
148
     */
149
    public function updateSection(Section $section, SectionUpdateStruct $sectionUpdateStruct)
150
    {
151 View Code Duplication
        if ($sectionUpdateStruct->name !== null && !is_string($sectionUpdateStruct->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...
152
            throw new InvalidArgumentValue('name', $section->name, 'Section');
153
        }
154
155 View Code Duplication
        if ($sectionUpdateStruct->identifier !== null && !is_string($sectionUpdateStruct->identifier)) {
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...
156
            throw new InvalidArgumentValue('identifier', $section->identifier, 'Section');
157
        }
158
159
        if (!$this->permissionResolver->canUser('section', 'edit', $section)) {
160
            throw new UnauthorizedException('section', 'edit');
161
        }
162
163
        if ($sectionUpdateStruct->identifier !== null) {
164
            try {
165
                $existingSection = $this->loadSectionByIdentifier($sectionUpdateStruct->identifier);
166
167
                // Allowing identifier update only for the same section
168
                if ($existingSection->id != $section->id) {
169
                    throw new InvalidArgumentException('sectionUpdateStruct', 'section with specified identifier already exists');
170
                }
171
            } catch (APINotFoundException $e) {
172
                // Do nothing
173
            }
174
        }
175
176
        $loadedSection = $this->loadSection($section->id);
177
178
        $this->repository->beginTransaction();
179
        try {
180
            $spiSection = $this->sectionHandler->update(
181
                $loadedSection->id,
182
                $sectionUpdateStruct->name ?: $loadedSection->name,
183
                $sectionUpdateStruct->identifier ?: $loadedSection->identifier
184
            );
185
            $this->repository->commit();
186
        } catch (Exception $e) {
187
            $this->repository->rollback();
188
            throw $e;
189
        }
190
191
        return $this->buildDomainSectionObject($spiSection);
192
    }
193
194
    /**
195
     * Loads a Section from its id ($sectionId).
196
     *
197
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if section could not be found
198
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed to read a section
199
     *
200
     * @param mixed $sectionId
201
     *
202
     * @return \eZ\Publish\API\Repository\Values\Content\Section
203
     */
204
    public function loadSection($sectionId)
205
    {
206
        $section = $this->buildDomainSectionObject(
207
            $this->sectionHandler->load($sectionId)
208
        );
209
210
        if (!$this->permissionResolver->canUser('section', 'view', $section)) {
211
            throw new UnauthorizedException('section', 'view');
212
        }
213
214
        return $section;
215
    }
216
217
    /**
218
     * Loads all sections.
219
     *
220
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed to read a section
221
     *
222
     * @return \eZ\Publish\API\Repository\Values\Content\Section[]
223
     */
224
    public function loadSections()
225
    {
226
        $sections = [];
227
        foreach ($this->sectionHandler->loadAll() as $spiSection) {
228
            $sections[] = $section = $this->buildDomainSectionObject($spiSection);
229
230
            // @todo change API to just filter instead of throwing here
231
            if (!$this->permissionResolver->canUser('section', 'view', $section)) {
232
                throw new UnauthorizedException('section', 'view');
233
            }
234
        }
235
236
        return $sections;
237
    }
238
239
    /**
240
     * Loads a Section from its identifier ($sectionIdentifier).
241
     *
242
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if section could not be found
243
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user user is not allowed to read a section
244
     *
245
     * @param string $sectionIdentifier
246
     *
247
     * @return \eZ\Publish\API\Repository\Values\Content\Section
248
     */
249
    public function loadSectionByIdentifier($sectionIdentifier)
250
    {
251
        if (!is_string($sectionIdentifier) || empty($sectionIdentifier)) {
252
            throw new InvalidArgumentValue('sectionIdentifier', $sectionIdentifier);
253
        }
254
255
        $section = $this->buildDomainSectionObject(
256
            $this->sectionHandler->loadByIdentifier($sectionIdentifier)
257
        );
258
259
        if (!$this->permissionResolver->canUser('section', 'view', $section)) {
260
            throw new UnauthorizedException('section', 'view');
261
        }
262
263
        return $section;
264
    }
265
266
    /**
267
     * Counts the contents which $section is assigned to.
268
     *
269
     * @param \eZ\Publish\API\Repository\Values\Content\Section $section
270
     *
271
     * @return int
272
     *
273
     * @deprecated since 6.0
274
     */
275
    public function countAssignedContents(Section $section)
276
    {
277
        return $this->sectionHandler->assignmentsCount($section->id);
278
    }
279
280
    /**
281
     * Returns true if the given section is assigned to contents, or used in role policies, or in role assignments.
282
     *
283
     * This does not check user permissions.
284
     *
285
     * @since 6.0
286
     *
287
     * @param \eZ\Publish\API\Repository\Values\Content\Section $section
288
     *
289
     * @return bool
290
     */
291
    public function isSectionUsed(Section $section)
292
    {
293
        return $this->sectionHandler->assignmentsCount($section->id) > 0 ||
294
               $this->sectionHandler->policiesCount($section->id) > 0 ||
295
               $this->sectionHandler->countRoleAssignmentsUsingSection($section->id) > 0;
296
    }
297
298
    /**
299
     * Assigns the content to the given section
300
     * this method overrides the current assigned section.
301
     *
302
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If user does not have access to view provided object
303
     *
304
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo
305
     * @param \eZ\Publish\API\Repository\Values\Content\Section $section
306
     */
307
    public function assignSection(ContentInfo $contentInfo, Section $section)
308
    {
309
        $loadedContentInfo = $this->repository->getContentService()->loadContentInfo($contentInfo->id);
310
        $loadedSection = $this->loadSection($section->id);
311
312
        if (!$this->permissionResolver->canUser('section', 'assign', $loadedContentInfo, [$loadedSection])) {
313
            throw new UnauthorizedException(
314
                'section',
315
                'assign',
316
                array(
317
                    'name' => $loadedSection->name,
318
                    'content-name' => $loadedContentInfo->name,
319
                )
320
            );
321
        }
322
323
        $this->repository->beginTransaction();
324
        try {
325
            $this->sectionHandler->assign(
326
                $loadedSection->id,
327
                $loadedContentInfo->id
328
            );
329
            $this->repository->commit();
330
        } catch (Exception $e) {
331
            $this->repository->rollback();
332
            throw $e;
333
        }
334
    }
335
336
    /**
337
     * Assigns the subtree to the given section
338
     * this method overrides the current assigned section.
339
     *
340
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
341
     *
342
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
343
     * @param \eZ\Publish\API\Repository\Values\Content\Section $section
344
     */
345
    public function assignSectionToSubtree(Location $location, Section $section): void
346
    {
347
        $loadedSubtree = $this->repository->getLocationService()->loadLocation($location->id);
348
        $loadedSection = $this->loadSection($section->id);
349
350
        /**
351
         * Check read access to whole source subtree.
352
         *
353
         * @var bool|\eZ\Publish\API\Repository\Values\Content\Query\Criterion
354
         */
355
        $sectionAssignCriterion = $this->permissionCriterionResolver->getPermissionsCriterion(
356
            'section', 'assign', [$loadedSection]
357
        );
358
        if ($sectionAssignCriterion === false) {
359
            throw new UnauthorizedException('section', 'assign', [
360
                'name' => $loadedSection->name,
361
                'subtree' => $loadedSubtree->pathString,
362
            ]);
363
        } elseif ($sectionAssignCriterion !== true) {
364
            // Query if there are any content in subtree current user don't have access to
365
            $query = new Query(
366
                [
367
                    'limit' => 0,
368
                    'filter' => new CriterionLogicalAnd(
369
                        [
370
                            new CriterionSubtree($loadedSubtree->pathString),
371
                            new CriterionLogicalNot($sectionAssignCriterion),
0 ignored issues
show
Bug introduced by
It seems like $sectionAssignCriterion defined by $this->permissionCriteri... array($loadedSection)) on line 355 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...
372
                        ]
373
                    ),
374
                ]
375
            );
376
377
            $result = $this->repository->getSearchService()->findContent($query, [], false);
378
            if ($result->totalCount > 0) {
379
                throw new UnauthorizedException('section', 'assign', [
380
                    'name' => $loadedSection->name,
381
                    'subtree' => $loadedSubtree->pathString,
382
                ]);
383
            }
384
        }
385
386
        $this->repository->beginTransaction();
387
        try {
388
            $this->locationHandler->setSectionForSubtree(
389
                $loadedSubtree->id,
390
                $loadedSection->id
391
            );
392
            $this->repository->commit();
393
        } catch (Exception $e) {
394
            $this->repository->rollback();
395
            throw $e;
396
        }
397
    }
398
399
    /**
400
     * Deletes $section from content repository.
401
     *
402
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException If the specified section is not found
403
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException If the current user is not allowed to delete a section
404
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException If section can not be deleted
405
     *         because it is still assigned to some contents,
406
     *         or because it is still being used in policy limitations.
407
     *
408
     * @param \eZ\Publish\API\Repository\Values\Content\Section $section
409
     */
410
    public function deleteSection(Section $section)
411
    {
412
        $loadedSection = $this->loadSection($section->id);
413
414
        if (!$this->permissionResolver->canUser('section', 'edit', $loadedSection)) {
415
            throw new UnauthorizedException('section', 'edit', array('sectionId' => $loadedSection->id));
416
        }
417
418
        if ($this->sectionHandler->assignmentsCount($loadedSection->id) > 0) {
419
            throw new BadStateException('section', 'section is still assigned to content');
420
        }
421
422
        if ($this->sectionHandler->policiesCount($loadedSection->id) > 0) {
423
            throw new BadStateException('section', 'section is still being used in policy limitations');
424
        }
425
426
        $this->repository->beginTransaction();
427
        try {
428
            $this->sectionHandler->delete($loadedSection->id);
429
            $this->repository->commit();
430
        } catch (Exception $e) {
431
            $this->repository->rollback();
432
            throw $e;
433
        }
434
    }
435
436
    /**
437
     * Instantiates a new SectionCreateStruct.
438
     *
439
     * @return \eZ\Publish\API\Repository\Values\Content\SectionCreateStruct
440
     */
441
    public function newSectionCreateStruct()
442
    {
443
        return new SectionCreateStruct();
444
    }
445
446
    /**
447
     * Instantiates a new SectionUpdateStruct.
448
     *
449
     * @return \eZ\Publish\API\Repository\Values\Content\SectionUpdateStruct
450
     */
451
    public function newSectionUpdateStruct()
452
    {
453
        return new SectionUpdateStruct();
454
    }
455
456
    /**
457
     * Builds API Section object from provided SPI Section object.
458
     *
459
     * @param \eZ\Publish\SPI\Persistence\Content\Section $spiSection
460
     *
461
     * @return \eZ\Publish\API\Repository\Values\Content\Section
462
     */
463
    protected function buildDomainSectionObject(SPISection $spiSection)
464
    {
465
        return new Section(
466
            array(
467
                'id' => $spiSection->id,
468
                'identifier' => $spiSection->identifier,
469
                'name' => $spiSection->name,
470
            )
471
        );
472
    }
473
}
474