Completed
Push — master ( 1912ba...680ed8 )
by André
52:04 queued 38:06
created

Handler::isMainLocation()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 6
rs 9.4285
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 = array();
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
     *
215
     * @return Location the newly created Location.
216
     */
217
    public function copySubtree($sourceId, $destinationParentId)
218
    {
219
        $children = $this->locationGateway->getSubtreeContent($sourceId);
220
        $destinationParentData = $this->locationGateway->getBasicNodeData($destinationParentId);
221
        $defaultObjectStates = $this->getDefaultContentStates();
222
        $contentMap = array();
223
        $locationMap = array(
224
            $children[0]['parent_node_id'] => array(
225
                'id' => $destinationParentId,
226
                'hidden' => (bool)$destinationParentData['is_hidden'],
227
                'invisible' => (bool)$destinationParentData['is_invisible'],
228
                'path_identification_string' => $destinationParentData['path_identification_string'],
229
            ),
230
        );
231
232
        $locations = array();
233
        foreach ($children as $child) {
234
            $locations[$child['contentobject_id']][$child['node_id']] = true;
235
        }
236
237
        $time = time();
238
        $mainLocations = array();
239
        $mainLocationsUpdate = array();
240
        foreach ($children as $index => $child) {
241
            // Copy content
242
            if (!isset($contentMap[$child['contentobject_id']])) {
243
                $content = $this->contentHandler->copy(
244
                    $child['contentobject_id'],
245
                    $child['contentobject_version']
246
                );
247
248
                $this->setContentStates($content, $defaultObjectStates);
249
250
                $content = $this->contentHandler->publish(
251
                    $content->versionInfo->contentInfo->id,
252
                    $content->versionInfo->contentInfo->currentVersionNo,
253
                    new MetadataUpdateStruct(
254
                        array(
255
                            'publicationDate' => $time,
256
                            'modificationDate' => $time,
257
                        )
258
                    )
259
                );
260
261
                $contentMap[$child['contentobject_id']] = $content->versionInfo->contentInfo->id;
262
            }
263
264
            $createStruct = $this->locationMapper->getLocationCreateStruct($child);
265
            $createStruct->contentId = $contentMap[$child['contentobject_id']];
266
            $parentData = $locationMap[$child['parent_node_id']];
267
            $createStruct->parentId = $parentData['id'];
268
            $createStruct->invisible = $createStruct->hidden || $parentData['hidden'] || $parentData['invisible'];
269
            $pathString = explode('/', $child['path_identification_string']);
270
            $pathString = end($pathString);
271
            $createStruct->pathIdentificationString = strlen($pathString) > 0
0 ignored issues
show
Deprecated Code introduced by
The property eZ\Publish\SPI\Persisten...athIdentificationString has been deprecated with message: Since 5.4, planned to be removed in 6.0

This property 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 property will be removed from the class and what other property to use instead.

Loading history...
272
                ? $parentData['path_identification_string'] . '/' . $pathString
273
                : null;
274
275
            // Use content main location if already set, otherwise create location as main
276
            if (isset($mainLocations[$child['contentobject_id']])) {
277
                $createStruct->mainLocationId = $locationMap[$mainLocations[$child['contentobject_id']]]['id'];
278
            } else {
279
                $createStruct->mainLocationId = true;
280
                $mainLocations[$child['contentobject_id']] = $child['node_id'];
281
282
                // If needed mark for update
283
                if (
284
                    isset($locations[$child['contentobject_id']][$child['main_node_id']]) &&
285
                    count($locations[$child['contentobject_id']]) > 1 &&
286
                    $child['node_id'] !== $child['main_node_id']
287
                ) {
288
                    $mainLocationsUpdate[$child['contentobject_id']] = $child['main_node_id'];
289
                }
290
            }
291
292
            $newLocation = $this->create($createStruct);
293
294
            $locationMap[$child['node_id']] = array(
295
                'id' => $newLocation->id,
296
                'hidden' => $newLocation->hidden,
297
                'invisible' => $newLocation->invisible,
298
                'path_identification_string' => $newLocation->pathIdentificationString,
0 ignored issues
show
Deprecated Code introduced by
The property eZ\Publish\SPI\Persisten...athIdentificationString has been deprecated with message: Since 5.4, planned to be removed in 6.0

This property 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 property will be removed from the class and what other property to use instead.

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