Completed
Push — ezp30878_cant_add_image_with_p... ( e19ea7...263f1b )
by
unknown
20:16
created

Handler::swap()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 2
dl 0
loc 4
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
     * Loads the data for the location identified by $locationId.
102
     *
103
     * @param int $locationId
104
     *
105
     * @return \eZ\Publish\SPI\Persistence\Content\Location
106
     */
107
    public function load($locationId)
108
    {
109
        return $this->treeHandler->loadLocation($locationId);
110
    }
111
112
    /**
113
     * Loads the subtree ids of the location identified by $locationId.
114
     *
115
     * @param int $locationId
116
     *
117
     * @return array Location ids are in the index, Content ids in the value.
118
     */
119
    public function loadSubtreeIds($locationId)
120
    {
121
        return $this->locationGateway->getSubtreeContent($locationId, true);
122
    }
123
124
    /**
125
     * Loads the data for the location identified by $remoteId.
126
     *
127
     * @param string $remoteId
128
     *
129
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
130
     *
131
     * @return \eZ\Publish\SPI\Persistence\Content\Location
132
     */
133
    public function loadByRemoteId($remoteId)
134
    {
135
        $data = $this->locationGateway->getBasicNodeDataByRemoteId($remoteId);
136
137
        return $this->locationMapper->createLocationFromRow($data);
138
    }
139
140
    /**
141
     * Loads all locations for $contentId, optionally limited to a sub tree
142
     * identified by $rootLocationId.
143
     *
144
     * @param int $contentId
145
     * @param int $rootLocationId
146
     *
147
     * @return \eZ\Publish\SPI\Persistence\Content\Location[]
148
     */
149
    public function loadLocationsByContent($contentId, $rootLocationId = null)
150
    {
151
        $rows = $this->locationGateway->loadLocationDataByContent($contentId, $rootLocationId);
152
153
        return $this->locationMapper->createLocationsFromRows($rows);
154
    }
155
156
    /**
157
     * @see \eZ\Publish\SPI\Persistence\Content\Location\Handler::loadParentLocationsForDraftContent
158
     */
159
    public function loadParentLocationsForDraftContent($contentId)
160
    {
161
        $rows = $this->locationGateway->loadParentLocationsDataForDraftContent($contentId);
162
163
        return $this->locationMapper->createLocationsFromRows($rows);
164
    }
165
166
    /**
167
     * Returns an array of default content states with content state group id as key.
168
     *
169
     * @return \eZ\Publish\SPI\Persistence\Content\ObjectState[]
170
     */
171
    protected function getDefaultContentStates()
172
    {
173
        $defaultObjectStatesMap = [];
174
175
        foreach ($this->objectStateHandler->loadAllGroups() as $objectStateGroup) {
176
            foreach ($this->objectStateHandler->loadObjectStates($objectStateGroup->id) as $objectState) {
177
                // Only register the first object state which is the default one.
178
                $defaultObjectStatesMap[$objectStateGroup->id] = $objectState;
179
                break;
180
            }
181
        }
182
183
        return $defaultObjectStatesMap;
184
    }
185
186
    /**
187
     * @param Content $content
188
     * @param \eZ\Publish\SPI\Persistence\Content\ObjectState[] $contentStates
189
     */
190
    protected function setContentStates(Content $content, array $contentStates)
191
    {
192
        foreach ($contentStates as $contentStateGroupId => $contentState) {
193
            $this->objectStateHandler->setContentState(
194
                $content->versionInfo->contentInfo->id,
195
                $contentStateGroupId,
196
                $contentState->id
197
            );
198
        }
199
    }
200
201
    /**
202
     * Copy location object identified by $sourceId, into destination identified by $destinationParentId.
203
     *
204
     * Performs a deep copy of the location identified by $sourceId and all of
205
     * its child locations, copying the most recent published content object
206
     * for each location to a new content object without any additional version
207
     * information. Relations are not copied. URLs are not touched at all.
208
     *
209
     * @todo Either move to async/batch or find ways toward optimizing away operations per object.
210
     * @todo Optionally retain dates and set creator
211
     *
212
     * @param mixed $sourceId
213
     * @param mixed $destinationParentId
214
     * @param int|null $newOwnerId
215
     *
216
     * @return Location the newly created Location.
217
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
218
     */
219
    public function copySubtree($sourceId, $destinationParentId, $newOwnerId = null)
220
    {
221
        $children = $this->locationGateway->getSubtreeContent($sourceId);
222
        $destinationParentData = $this->locationGateway->getBasicNodeData($destinationParentId);
223
        $defaultObjectStates = $this->getDefaultContentStates();
224
        $contentMap = [];
225
        $locationMap = [
226
            $children[0]['parent_node_id'] => [
227
                'id' => $destinationParentId,
228
                'hidden' => (bool)$destinationParentData['is_hidden'],
229
                'invisible' => (bool)$destinationParentData['is_invisible'],
230
                'path_identification_string' => $destinationParentData['path_identification_string'],
231
            ],
232
        ];
233
234
        $locations = [];
235
        foreach ($children as $child) {
236
            $locations[$child['contentobject_id']][$child['node_id']] = true;
237
        }
238
239
        $time = time();
240
        $mainLocations = [];
241
        $mainLocationsUpdate = [];
242
        foreach ($children as $index => $child) {
243
            // Copy content
244
            if (!isset($contentMap[$child['contentobject_id']])) {
245
                $content = $this->contentHandler->copy(
246
                    $child['contentobject_id'],
247
                    $child['contentobject_version'],
248
                    $newOwnerId
249
                );
250
251
                $this->setContentStates($content, $defaultObjectStates);
252
253
                $content = $this->contentHandler->publish(
254
                    $content->versionInfo->contentInfo->id,
255
                    $content->versionInfo->contentInfo->currentVersionNo,
256
                    new MetadataUpdateStruct(
257
                        [
258
                            'publicationDate' => $time,
259
                            'modificationDate' => $time,
260
                        ]
261
                    )
262
                );
263
264
                $contentMap[$child['contentobject_id']] = $content->versionInfo->contentInfo->id;
265
            }
266
267
            $createStruct = $this->locationMapper->getLocationCreateStruct($child);
268
            $createStruct->contentId = $contentMap[$child['contentobject_id']];
269
            $parentData = $locationMap[$child['parent_node_id']];
270
            $createStruct->parentId = $parentData['id'];
271
            $createStruct->invisible = $createStruct->hidden || $parentData['hidden'] || $parentData['invisible'];
272
            $pathString = explode('/', $child['path_identification_string']);
273
            $pathString = end($pathString);
274
            $createStruct->pathIdentificationString = strlen($pathString) > 0
275
                ? $parentData['path_identification_string'] . '/' . $pathString
276
                : null;
277
278
            // Use content main location if already set, otherwise create location as main
279
            if (isset($mainLocations[$child['contentobject_id']])) {
280
                $createStruct->mainLocationId = $locationMap[$mainLocations[$child['contentobject_id']]]['id'];
281
            } else {
282
                $createStruct->mainLocationId = true;
283
                $mainLocations[$child['contentobject_id']] = $child['node_id'];
284
285
                // If needed mark for update
286
                if (
287
                    isset($locations[$child['contentobject_id']][$child['main_node_id']]) &&
288
                    count($locations[$child['contentobject_id']]) > 1 &&
289
                    $child['node_id'] !== $child['main_node_id']
290
                ) {
291
                    $mainLocationsUpdate[$child['contentobject_id']] = $child['main_node_id'];
292
                }
293
            }
294
295
            $newLocation = $this->create($createStruct);
296
297
            $locationMap[$child['node_id']] = [
298
                'id' => $newLocation->id,
299
                'hidden' => $newLocation->hidden,
300
                'invisible' => $newLocation->invisible,
301
                'path_identification_string' => $newLocation->pathIdentificationString,
302
            ];
303
            if ($index === 0) {
304
                $copiedSubtreeRootLocation = $newLocation;
305
            }
306
        }
307
308
        // Update main locations
309
        foreach ($mainLocationsUpdate as $contentId => $mainLocationId) {
310
            $this->changeMainLocation(
311
                $contentMap[$contentId],
312
                $locationMap[$mainLocationId]['id']
313
            );
314
        }
315
316
        $destinationParentSectionId = $this->getSectionId($destinationParentId);
317
        $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...
318
319
        return $copiedSubtreeRootLocation;
320
    }
321
322
    /**
323
     * Retrieves section ID of the location's content.
324
     *
325
     * @param int $locationId
326
     *
327
     * @return int
328
     */
329
    private function getSectionId($locationId)
330
    {
331
        $location = $this->load($locationId);
332
        $locationContentInfo = $this->contentHandler->loadContentInfo($location->contentId);
333
334
        return $locationContentInfo->sectionId;
335
    }
336
337
    /**
338
     * If the location is the main location for its content, updates subtree section.
339
     *
340
     * @param Location $location
341
     * @param int $sectionId
342
     */
343
    private function updateSubtreeSectionIfNecessary(Location $location, $sectionId)
344
    {
345
        if ($this->isMainLocation($location)) {
346
            $this->setSectionForSubtree($location->id, $sectionId);
347
        }
348
    }
349
350
    /**
351
     * Checks if the location is the main location for its content.
352
     *
353
     * @param Location $location
354
     *
355
     * @return bool
356
     */
357
    private function isMainLocation(Location $location)
358
    {
359
        $locationContentInfo = $this->contentHandler->loadContentInfo($location->contentId);
360
361
        return $locationContentInfo->mainLocationId === $location->id;
362
    }
363
364
    /**
365
     * Moves location identified by $sourceId into new parent identified by $destinationParentId.
366
     *
367
     * Performs a full move of the location identified by $sourceId to a new
368
     * destination, identified by $destinationParentId. Relations do not need
369
     * to be updated, since they refer to Content. URLs are not touched.
370
     *
371
     * @param mixed $sourceId
372
     * @param mixed $destinationParentId
373
     *
374
     * @return bool
375
     */
376
    public function move($sourceId, $destinationParentId)
377
    {
378
        $sourceNodeData = $this->locationGateway->getBasicNodeData($sourceId);
379
        $destinationNodeData = $this->locationGateway->getBasicNodeData($destinationParentId);
380
381
        $this->locationGateway->moveSubtreeNodes(
382
            $sourceNodeData,
383
            $destinationNodeData
384
        );
385
386
        $this->locationGateway->updateNodeAssignment(
387
            $sourceNodeData['contentobject_id'],
388
            $sourceNodeData['parent_node_id'],
389
            $destinationParentId,
390
            Gateway::NODE_ASSIGNMENT_OP_CODE_MOVE
391
        );
392
393
        $sourceLocation = $this->load($sourceId);
394
        $destinationParentSectionId = $this->getSectionId($destinationParentId);
395
        $this->updateSubtreeSectionIfNecessary($sourceLocation, $destinationParentSectionId);
396
    }
397
398
    /**
399
     * Marks the given nodes and all ancestors as modified.
400
     *
401
     * Optionally a time stamp with the modification date may be specified,
402
     * otherwise the current time is used.
403
     *
404
     * @param int|string $locationId
405
     * @param int $timestamp
406
     */
407
    public function markSubtreeModified($locationId, $timestamp = null)
408
    {
409
        $nodeData = $this->locationGateway->getBasicNodeData($locationId);
410
        $timestamp = $timestamp ?: time();
411
        $this->locationGateway->updateSubtreeModificationTime($nodeData['path_string'], $timestamp);
412
    }
413
414
    /**
415
     * Sets a location to be hidden, and it self + all children to invisible.
416
     *
417
     * @param mixed $id Location ID
418
     */
419
    public function hide($id)
420
    {
421
        $sourceNodeData = $this->locationGateway->getBasicNodeData($id);
422
423
        $this->locationGateway->hideSubtree($sourceNodeData['path_string']);
424
    }
425
426
    /**
427
     * Sets a location to be unhidden, and self + children to visible unless a parent is hiding the tree.
428
     * If not make sure only children down to first hidden node is marked visible.
429
     *
430
     * @param mixed $id
431
     */
432
    public function unHide($id)
433
    {
434
        $sourceNodeData = $this->locationGateway->getBasicNodeData($id);
435
436
        $this->locationGateway->unhideSubtree($sourceNodeData['path_string']);
437
    }
438
439
    /**
440
     * Swaps the content object being pointed to by a location object.
441
     *
442
     * Make the location identified by $locationId1 refer to the Content
443
     * referred to by $locationId2 and vice versa.
444
     *
445
     * @param mixed $locationId1
446
     * @param mixed $locationId2
447
     *
448
     * @return bool
449
     */
450
    public function swap($locationId1, $locationId2)
451
    {
452
        $this->locationGateway->swap($locationId1, $locationId2);
453
    }
454
455
    /**
456
     * Updates an existing location.
457
     *
458
     * @param \eZ\Publish\SPI\Persistence\Content\Location\UpdateStruct $location
459
     * @param int $locationId
460
     */
461
    public function update(UpdateStruct $location, $locationId)
462
    {
463
        $this->locationGateway->update($location, $locationId);
464
    }
465
466
    /**
467
     * Creates a new location rooted at $location->parentId.
468
     *
469
     * @param \eZ\Publish\SPI\Persistence\Content\Location\CreateStruct $createStruct
470
     *
471
     * @return \eZ\Publish\SPI\Persistence\Content\Location
472
     */
473
    public function create(CreateStruct $createStruct)
474
    {
475
        $parentNodeData = $this->locationGateway->getBasicNodeData($createStruct->parentId);
476
        $locationStruct = $this->locationGateway->create($createStruct, $parentNodeData);
477
        $this->locationGateway->createNodeAssignment(
478
            $createStruct,
479
            $parentNodeData['node_id'],
480
            LocationGateway::NODE_ASSIGNMENT_OP_CODE_CREATE_NOP
481
        );
482
483
        return $locationStruct;
484
    }
485
486
    /**
487
     * Removes all Locations under and including $locationId.
488
     *
489
     * Performs a recursive delete on the location identified by $locationId,
490
     * including all of its child locations. Content which is not referred to
491
     * by any other location is automatically removed. Content which looses its
492
     * main Location will get the first of its other Locations assigned as the
493
     * new main Location.
494
     *
495
     * @param mixed $locationId
496
     *
497
     * @return bool
498
     */
499
    public function removeSubtree($locationId)
500
    {
501
        $this->treeHandler->removeSubtree($locationId);
502
    }
503
504
    /**
505
     * Set section on all content objects in the subtree.
506
     *
507
     * @param mixed $locationId
508
     * @param mixed $sectionId
509
     */
510
    public function setSectionForSubtree($locationId, $sectionId)
511
    {
512
        $this->treeHandler->setSectionForSubtree($locationId, $sectionId);
513
    }
514
515
    /**
516
     * Changes main location of content identified by given $contentId to location identified by given $locationId.
517
     *
518
     * Updates ezcontentobject_tree and eznode_assignment tables (eznode_assignment for content current version number).
519
     *
520
     * @param mixed $contentId
521
     * @param mixed $locationId
522
     */
523
    public function changeMainLocation($contentId, $locationId)
524
    {
525
        $this->treeHandler->changeMainLocation($contentId, $locationId);
526
    }
527
528
    /**
529
     * Get the total number of all existing Locations. Can be combined with loadAllLocations.
530
     *
531
     * @return int
532
     */
533
    public function countAllLocations()
534
    {
535
        return $this->locationGateway->countAllLocations();
536
    }
537
538
    /**
539
     * Bulk-load all existing Locations, constrained by $limit and $offset to paginate results.
540
     *
541
     * @param int $offset
542
     * @param int $limit
543
     *
544
     * @return \eZ\Publish\SPI\Persistence\Content\Location[]
545
     */
546
    public function loadAllLocations($offset, $limit)
547
    {
548
        $rows = $this->locationGateway->loadAllLocationsData($offset, $limit);
549
550
        return $this->locationMapper->createLocationsFromRows($rows);
551
    }
552
}
553