Completed
Push — ezp_30300 ( ef176f...d1dcf2 )
by
unknown
26:24
created

Handler::hide()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * File containing the Location Handler 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\Persistence\Legacy\Content\Location;
10
11
use eZ\Publish\SPI\Persistence\Content;
12
use eZ\Publish\SPI\Persistence\Content\Location;
13
use eZ\Publish\SPI\Persistence\Content\Location\CreateStruct;
14
use eZ\Publish\SPI\Persistence\Content\Location\UpdateStruct;
15
use eZ\Publish\SPI\Persistence\Content\Location\Handler as BaseLocationHandler;
16
use eZ\Publish\Core\Persistence\Legacy\Content\Handler as ContentHandler;
17
use eZ\Publish\Core\Persistence\Legacy\Content\TreeHandler;
18
use eZ\Publish\Core\Persistence\Legacy\Content\ObjectState\Handler as ObjectStateHandler;
19
use eZ\Publish\Core\Persistence\Legacy\Content\Location\Gateway as LocationGateway;
20
use eZ\Publish\Core\Persistence\Legacy\Content\Location\Mapper as LocationMapper;
21
use eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct;
22
23
/**
24
 * The Location Handler interface defines operations on Location elements in the storage engine.
25
 */
26
class Handler implements BaseLocationHandler
27
{
28
    /**
29
     * Gateway for handling location data.
30
     *
31
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Location\Gateway
32
     */
33
    protected $locationGateway;
34
35
    /**
36
     * Location locationMapper.
37
     *
38
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Location\Mapper
39
     */
40
    protected $locationMapper;
41
42
    /**
43
     * Content handler.
44
     *
45
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Handler
46
     */
47
    protected $contentHandler;
48
49
    /**
50
     * Object state handler.
51
     *
52
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\ObjectState\Handler
53
     */
54
    protected $objectStateHandler;
55
56
    /**
57
     * Tree handler.
58
     *
59
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\TreeHandler
60
     */
61
    protected $treeHandler;
62
63
    /**
64
     * Construct from userGateway.
65
     *
66
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\Location\Gateway $locationGateway
67
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\Location\Mapper $locationMapper
68
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\Handler $contentHandler
69
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\ObjectState\Handler $objectStateHandler
70
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\TreeHandler $treeHandler
71
     *
72
     * @return \eZ\Publish\Core\Persistence\Legacy\Content\Location\Handler
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
73
     */
74
    public function __construct(
75
        LocationGateway $locationGateway,
76
        LocationMapper $locationMapper,
77
        ContentHandler $contentHandler,
78
        ObjectStateHandler $objectStateHandler,
79
        TreeHandler $treeHandler
80
    ) {
81
        $this->locationGateway = $locationGateway;
82
        $this->locationMapper = $locationMapper;
83
        $this->contentHandler = $contentHandler;
84
        $this->objectStateHandler = $objectStateHandler;
85
        $this->treeHandler = $treeHandler;
86
    }
87
88
    /**
89
     * Returns parent path string for a path string.
90
     *
91
     * @param string $pathString
92
     *
93
     * @return string
94
     */
95
    protected function getParentPathString($pathString)
96
    {
97
        return implode('/', array_slice(explode('/', $pathString), 0, -2)) . '/';
98
    }
99
100
    /**
101
     * {@inheritdoc}
102
     */
103
    public function load($locationId, array $translations = null, bool $useAlwaysAvailable = true)
104
    {
105
        return $this->treeHandler->loadLocation($locationId, $translations, $useAlwaysAvailable);
0 ignored issues
show
Bug introduced by
It seems like $translations defined by parameter $translations on line 103 can also be of type array; however, eZ\Publish\Core\Persiste...Handler::loadLocation() does only seem to accept null|array<integer,string>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
106
    }
107
108
    /**
109
     * Loads the subtree ids of the location identified by $locationId.
110
     *
111
     * @param int $locationId
112
     *
113
     * @return array Location ids are in the index, Content ids in the value.
114
     */
115
    public function loadSubtreeIds($locationId)
116
    {
117
        return $this->locationGateway->getSubtreeContent($locationId, true);
118
    }
119
120
    /**
121
     * {@inheritdoc}
122
     */
123
    public function loadByRemoteId($remoteId, array $translations = null, bool $useAlwaysAvailable = true)
124
    {
125
        $data = $this->locationGateway->getBasicNodeDataByRemoteId($remoteId, $translations, $useAlwaysAvailable);
0 ignored issues
show
Bug introduced by
It seems like $translations defined by parameter $translations on line 123 can also be of type array; however, eZ\Publish\Core\Persiste...sicNodeDataByRemoteId() does only seem to accept null|array<integer,string>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
126
127
        return $this->locationMapper->createLocationFromRow($data);
128
    }
129
130
    /**
131
     * Loads all locations for $contentId, optionally limited to a sub tree
132
     * identified by $rootLocationId.
133
     *
134
     * @param int $contentId
135
     * @param int $rootLocationId
136
     *
137
     * @return \eZ\Publish\SPI\Persistence\Content\Location[]
138
     */
139
    public function loadLocationsByContent($contentId, $rootLocationId = null)
140
    {
141
        $rows = $this->locationGateway->loadLocationDataByContent($contentId, $rootLocationId);
142
143
        return $this->locationMapper->createLocationsFromRows($rows);
144
    }
145
146
    /**
147
     * @see \eZ\Publish\SPI\Persistence\Content\Location\Handler::loadParentLocationsForDraftContent
148
     */
149
    public function loadParentLocationsForDraftContent($contentId)
150
    {
151
        $rows = $this->locationGateway->loadParentLocationsDataForDraftContent($contentId);
152
153
        return $this->locationMapper->createLocationsFromRows($rows);
154
    }
155
156
    /**
157
     * Returns an array of default content states with content state group id as key.
158
     *
159
     * @return \eZ\Publish\SPI\Persistence\Content\ObjectState[]
160
     */
161
    protected function getDefaultContentStates()
162
    {
163
        $defaultObjectStatesMap = array();
164
165
        foreach ($this->objectStateHandler->loadAllGroups() as $objectStateGroup) {
166
            foreach ($this->objectStateHandler->loadObjectStates($objectStateGroup->id) as $objectState) {
167
                // Only register the first object state which is the default one.
168
                $defaultObjectStatesMap[$objectStateGroup->id] = $objectState;
169
                break;
170
            }
171
        }
172
173
        return $defaultObjectStatesMap;
174
    }
175
176
    /**
177
     * @param Content $content
178
     * @param \eZ\Publish\SPI\Persistence\Content\ObjectState[] $contentStates
179
     */
180
    protected function setContentStates(Content $content, array $contentStates)
181
    {
182
        foreach ($contentStates as $contentStateGroupId => $contentState) {
183
            $this->objectStateHandler->setContentState(
184
                $content->versionInfo->contentInfo->id,
185
                $contentStateGroupId,
186
                $contentState->id
187
            );
188
        }
189
    }
190
191
    /**
192
     * Copy location object identified by $sourceId, into destination identified by $destinationParentId.
193
     *
194
     * Performs a deep copy of the location identified by $sourceId and all of
195
     * its child locations, copying the most recent published content object
196
     * for each location to a new content object without any additional version
197
     * information. Relations are not copied. URLs are not touched at all.
198
     *
199
     * @todo Either move to async/batch or find ways toward optimizing away operations per object.
200
     * @todo Optionally retain dates and set creator
201
     *
202
     * @param mixed $sourceId
203
     * @param mixed $destinationParentId
204
     * @param int|null $newOwnerId
205
     *
206
     * @return Location the newly created Location.
207
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
208
     */
209
    public function copySubtree($sourceId, $destinationParentId, $newOwnerId = null)
210
    {
211
        $children = $this->locationGateway->getSubtreeContent($sourceId);
212
        $destinationParentData = $this->locationGateway->getBasicNodeData($destinationParentId);
213
        $defaultObjectStates = $this->getDefaultContentStates();
214
        $contentMap = array();
215
        $locationMap = array(
216
            $children[0]['parent_node_id'] => array(
217
                'id' => $destinationParentId,
218
                'hidden' => (bool)$destinationParentData['is_hidden'],
219
                'invisible' => (bool)$destinationParentData['is_invisible'],
220
                'path_identification_string' => $destinationParentData['path_identification_string'],
221
            ),
222
        );
223
224
        $locations = array();
225
        foreach ($children as $child) {
226
            $locations[$child['contentobject_id']][$child['node_id']] = true;
227
        }
228
229
        $time = time();
230
        $mainLocations = array();
231
        $mainLocationsUpdate = array();
232
        foreach ($children as $index => $child) {
233
            // Copy content
234
            if (!isset($contentMap[$child['contentobject_id']])) {
235
                $content = $this->contentHandler->copy(
236
                    $child['contentobject_id'],
237
                    $child['contentobject_version'],
238
                    $newOwnerId
239
                );
240
241
                $this->setContentStates($content, $defaultObjectStates);
242
243
                $content = $this->contentHandler->publish(
244
                    $content->versionInfo->contentInfo->id,
245
                    $content->versionInfo->contentInfo->currentVersionNo,
246
                    new MetadataUpdateStruct(
247
                        array(
248
                            'publicationDate' => $time,
249
                            'modificationDate' => $time,
250
                        )
251
                    )
252
                );
253
254
                $contentMap[$child['contentobject_id']] = $content->versionInfo->contentInfo->id;
255
            }
256
257
            $createStruct = $this->locationMapper->getLocationCreateStruct($child);
258
            $createStruct->contentId = $contentMap[$child['contentobject_id']];
259
            $parentData = $locationMap[$child['parent_node_id']];
260
            $createStruct->parentId = $parentData['id'];
261
            $createStruct->invisible = $createStruct->hidden || $parentData['hidden'] || $parentData['invisible'];
262
            $pathString = explode('/', $child['path_identification_string']);
263
            $pathString = end($pathString);
264
            $createStruct->pathIdentificationString = strlen($pathString) > 0
265
                ? $parentData['path_identification_string'] . '/' . $pathString
266
                : null;
267
268
            // Use content main location if already set, otherwise create location as main
269
            if (isset($mainLocations[$child['contentobject_id']])) {
270
                $createStruct->mainLocationId = $locationMap[$mainLocations[$child['contentobject_id']]]['id'];
271
            } else {
272
                $createStruct->mainLocationId = true;
273
                $mainLocations[$child['contentobject_id']] = $child['node_id'];
274
275
                // If needed mark for update
276
                if (
277
                    isset($locations[$child['contentobject_id']][$child['main_node_id']]) &&
278
                    count($locations[$child['contentobject_id']]) > 1 &&
279
                    $child['node_id'] !== $child['main_node_id']
280
                ) {
281
                    $mainLocationsUpdate[$child['contentobject_id']] = $child['main_node_id'];
282
                }
283
            }
284
285
            $newLocation = $this->create($createStruct);
286
287
            $locationMap[$child['node_id']] = array(
288
                'id' => $newLocation->id,
289
                'hidden' => $newLocation->hidden,
290
                'invisible' => $newLocation->invisible,
291
                'path_identification_string' => $newLocation->pathIdentificationString,
292
            );
293
            if ($index === 0) {
294
                $copiedSubtreeRootLocation = $newLocation;
295
            }
296
        }
297
298
        // Update main locations
299
        foreach ($mainLocationsUpdate as $contentId => $mainLocationId) {
300
            $this->changeMainLocation(
301
                $contentMap[$contentId],
302
                $locationMap[$mainLocationId]['id']
303
            );
304
        }
305
306
        $destinationParentSectionId = $this->getSectionId($destinationParentId);
307
        $this->updateSubtreeSectionIfNecessary($copiedSubtreeRootLocation, $destinationParentSectionId);
0 ignored issues
show
Bug introduced by
The variable $copiedSubtreeRootLocation does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
308
309
        return $copiedSubtreeRootLocation;
310
    }
311
312
    /**
313
     * Retrieves section ID of the location's content.
314
     *
315
     * @param int $locationId
316
     *
317
     * @return int
318
     */
319
    private function getSectionId($locationId)
320
    {
321
        $location = $this->load($locationId);
322
        $locationContentInfo = $this->contentHandler->loadContentInfo($location->contentId);
323
324
        return $locationContentInfo->sectionId;
325
    }
326
327
    /**
328
     * If the location is the main location for its content, updates subtree section.
329
     *
330
     * @param Location $location
331
     * @param int $sectionId
332
     */
333
    private function updateSubtreeSectionIfNecessary(Location $location, $sectionId)
334
    {
335
        if ($this->isMainLocation($location)) {
336
            $this->setSectionForSubtree($location->id, $sectionId);
337
        }
338
    }
339
340
    /**
341
     * Checks if the location is the main location for its content.
342
     *
343
     * @param Location $location
344
     *
345
     * @return bool
346
     */
347
    private function isMainLocation(Location $location)
348
    {
349
        $locationContentInfo = $this->contentHandler->loadContentInfo($location->contentId);
350
351
        return $locationContentInfo->mainLocationId === $location->id;
352
    }
353
354
    /**
355
     * Moves location identified by $sourceId into new parent identified by $destinationParentId.
356
     *
357
     * Performs a full move of the location identified by $sourceId to a new
358
     * destination, identified by $destinationParentId. Relations do not need
359
     * to be updated, since they refer to Content. URLs are not touched.
360
     *
361
     * @param mixed $sourceId
362
     * @param mixed $destinationParentId
363
     *
364
     * @return bool
365
     */
366
    public function move($sourceId, $destinationParentId)
367
    {
368
        $sourceNodeData = $this->locationGateway->getBasicNodeData($sourceId);
369
        $destinationNodeData = $this->locationGateway->getBasicNodeData($destinationParentId);
370
371
        $this->locationGateway->moveSubtreeNodes(
372
            $sourceNodeData,
373
            $destinationNodeData
374
        );
375
376
        $this->locationGateway->updateNodeAssignment(
377
            $sourceNodeData['contentobject_id'],
378
            $sourceNodeData['parent_node_id'],
379
            $destinationParentId,
380
            Gateway::NODE_ASSIGNMENT_OP_CODE_MOVE
381
        );
382
383
        $sourceLocation = $this->load($sourceId);
384
        $destinationParentSectionId = $this->getSectionId($destinationParentId);
385
        $this->updateSubtreeSectionIfNecessary($sourceLocation, $destinationParentSectionId);
386
    }
387
388
    /**
389
     * Marks the given nodes and all ancestors as modified.
390
     *
391
     * Optionally a time stamp with the modification date may be specified,
392
     * otherwise the current time is used.
393
     *
394
     * @param int|string $locationId
395
     * @param int $timestamp
396
     */
397
    public function markSubtreeModified($locationId, $timestamp = null)
398
    {
399
        $nodeData = $this->locationGateway->getBasicNodeData($locationId);
400
        $timestamp = $timestamp ?: time();
401
        $this->locationGateway->updateSubtreeModificationTime($nodeData['path_string'], $timestamp);
402
    }
403
404
    /**
405
     * Sets a location to be hidden, and it self + all children to invisible.
406
     *
407
     * @param mixed $id Location ID
408
     */
409
    public function hide($id)
410
    {
411
        $sourceNodeData = $this->locationGateway->getBasicNodeData($id);
412
413
        $this->locationGateway->hideSubtree($sourceNodeData['path_string']);
414
    }
415
416
    /**
417
     * Sets a location to be unhidden, and self + children to visible unless a parent is hiding the tree.
418
     * If not make sure only children down to first hidden node is marked visible.
419
     *
420
     * @param mixed $id
421
     */
422
    public function unHide($id)
423
    {
424
        $sourceNodeData = $this->locationGateway->getBasicNodeData($id);
425
426
        $this->locationGateway->unhideSubtree($sourceNodeData['path_string']);
427
    }
428
429
    /**
430
     * Swaps the content object being pointed to by a location object.
431
     *
432
     * Make the location identified by $locationId1 refer to the Content
433
     * referred to by $locationId2 and vice versa.
434
     *
435
     * @param mixed $locationId1
436
     * @param mixed $locationId2
437
     *
438
     * @return bool
439
     */
440
    public function swap($locationId1, $locationId2)
441
    {
442
        $this->locationGateway->swap($locationId1, $locationId2);
443
    }
444
445
    /**
446
     * Updates an existing location.
447
     *
448
     * @param \eZ\Publish\SPI\Persistence\Content\Location\UpdateStruct $location
449
     * @param int $locationId
450
     */
451
    public function update(UpdateStruct $location, $locationId)
452
    {
453
        $this->locationGateway->update($location, $locationId);
454
    }
455
456
    /**
457
     * Creates a new location rooted at $location->parentId.
458
     *
459
     * @param \eZ\Publish\SPI\Persistence\Content\Location\CreateStruct $createStruct
460
     *
461
     * @return \eZ\Publish\SPI\Persistence\Content\Location
462
     */
463
    public function create(CreateStruct $createStruct)
464
    {
465
        $parentNodeData = $this->locationGateway->getBasicNodeData($createStruct->parentId);
466
        $locationStruct = $this->locationGateway->create($createStruct, $parentNodeData);
467
        $this->locationGateway->createNodeAssignment(
468
            $createStruct,
469
            $parentNodeData['node_id'],
470
            LocationGateway::NODE_ASSIGNMENT_OP_CODE_CREATE_NOP
471
        );
472
473
        return $locationStruct;
474
    }
475
476
    /**
477
     * Removes all Locations under and including $locationId.
478
     *
479
     * Performs a recursive delete on the location identified by $locationId,
480
     * including all of its child locations. Content which is not referred to
481
     * by any other location is automatically removed. Content which looses its
482
     * main Location will get the first of its other Locations assigned as the
483
     * new main Location.
484
     *
485
     * @param mixed $locationId
486
     *
487
     * @return bool
488
     */
489
    public function removeSubtree($locationId)
490
    {
491
        $this->treeHandler->removeSubtree($locationId);
492
    }
493
494
    /**
495
     * Set section on all content objects in the subtree.
496
     *
497
     * @param mixed $locationId
498
     * @param mixed $sectionId
499
     */
500
    public function setSectionForSubtree($locationId, $sectionId)
501
    {
502
        $this->treeHandler->setSectionForSubtree($locationId, $sectionId);
503
    }
504
505
    /**
506
     * Changes main location of content identified by given $contentId to location identified by given $locationId.
507
     *
508
     * Updates ezcontentobject_tree and eznode_assignment tables (eznode_assignment for content current version number).
509
     *
510
     * @param mixed $contentId
511
     * @param mixed $locationId
512
     */
513
    public function changeMainLocation($contentId, $locationId)
514
    {
515
        $this->treeHandler->changeMainLocation($contentId, $locationId);
516
    }
517
518
    /**
519
     * Get the total number of all existing Locations. Can be combined with loadAllLocations.
520
     *
521
     * @return int
522
     */
523
    public function countAllLocations()
524
    {
525
        return $this->locationGateway->countAllLocations();
526
    }
527
528
    /**
529
     * Bulk-load all existing Locations, constrained by $limit and $offset to paginate results.
530
     *
531
     * @param int $offset
532
     * @param int $limit
533
     *
534
     * @return \eZ\Publish\SPI\Persistence\Content\Location[]
535
     */
536
    public function loadAllLocations($offset, $limit)
537
    {
538
        $rows = $this->locationGateway->loadAllLocationsData($offset, $limit);
539
540
        return $this->locationMapper->createLocationsFromRows($rows);
541
    }
542
}
543