Completed
Push — ezp24624-query_controller_take... ( f346e1...647162 )
by
unknown
23:39
created

Handler::copySubtree()   F

Complexity

Conditions 14
Paths 584

Size

Total Lines 108
Code Lines 69

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 14
eloc 69
nc 584
nop 2
dl 0
loc 108
rs 2.5344
c 0
b 0
f 0

How to fix   Long Method    Complexity   

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 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
 * @version //autogentag//
10
 */
11
namespace eZ\Publish\Core\Persistence\Legacy\Content\Location;
12
13
use eZ\Publish\SPI\Persistence\Content;
14
use eZ\Publish\SPI\Persistence\Content\Location;
15
use eZ\Publish\SPI\Persistence\Content\Location\CreateStruct;
16
use eZ\Publish\SPI\Persistence\Content\Location\UpdateStruct;
17
use eZ\Publish\SPI\Persistence\Content\Location\Handler as BaseLocationHandler;
18
use eZ\Publish\Core\Persistence\Legacy\Content\Handler as ContentHandler;
19
use eZ\Publish\Core\Persistence\Legacy\Content\TreeHandler;
20
use eZ\Publish\Core\Persistence\Legacy\Content\ObjectState\Handler as ObjectStateHandler;
21
use eZ\Publish\Core\Persistence\Legacy\Content\Location\Gateway as LocationGateway;
22
use eZ\Publish\Core\Persistence\Legacy\Content\Location\Mapper as LocationMapper;
23
use eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct;
24
25
/**
26
 * The Location Handler interface defines operations on Location elements in the storage engine.
27
 */
28
class Handler implements BaseLocationHandler
29
{
30
    /**
31
     * Gateway for handling location data.
32
     *
33
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Location\Gateway
34
     */
35
    protected $locationGateway;
36
37
    /**
38
     * Location locationMapper.
39
     *
40
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Location\Mapper
41
     */
42
    protected $locationMapper;
43
44
    /**
45
     * Content handler.
46
     *
47
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Handler
48
     */
49
    protected $contentHandler;
50
51
    /**
52
     * Object state handler.
53
     *
54
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\ObjectState\Handler
55
     */
56
    protected $objectStateHandler;
57
58
    /**
59
     * Tree handler.
60
     *
61
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\TreeHandler
62
     */
63
    protected $treeHandler;
64
65
    /**
66
     * Construct from userGateway.
67
     *
68
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\Location\Gateway $locationGateway
69
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\Location\Mapper $locationMapper
70
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\Handler $contentHandler
71
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\ObjectState\Handler $objectStateHandler
72
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\TreeHandler $treeHandler
73
     *
74
     * @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...
75
     */
76
    public function __construct(
77
        LocationGateway $locationGateway,
78
        LocationMapper $locationMapper,
79
        ContentHandler $contentHandler,
80
        ObjectStateHandler $objectStateHandler,
81
        TreeHandler $treeHandler
82
    ) {
83
        $this->locationGateway = $locationGateway;
84
        $this->locationMapper = $locationMapper;
85
        $this->contentHandler = $contentHandler;
86
        $this->objectStateHandler = $objectStateHandler;
87
        $this->treeHandler = $treeHandler;
88
    }
89
90
    /**
91
     * Returns parent path string for a path string.
92
     *
93
     * @param string $pathString
94
     *
95
     * @return string
96
     */
97
    protected function getParentPathString($pathString)
98
    {
99
        return implode('/', array_slice(explode('/', $pathString), 0, -2)) . '/';
100
    }
101
102
    /**
103
     * Loads the data for the location identified by $locationId.
104
     *
105
     * @param int $locationId
106
     *
107
     * @return \eZ\Publish\SPI\Persistence\Content\Location
108
     */
109
    public function load($locationId)
110
    {
111
        return $this->treeHandler->loadLocation($locationId);
112
    }
113
114
    /**
115
     * Loads the subtree ids of the location identified by $locationId.
116
     *
117
     * @param int $locationId
118
     *
119
     * @return array Location ids are in the index, Content ids in the value.
120
     */
121
    public function loadSubtreeIds($locationId)
122
    {
123
        return $this->locationGateway->getSubtreeContent($locationId, true);
124
    }
125
126
    /**
127
     * Loads the data for the location identified by $remoteId.
128
     *
129
     * @param string $remoteId
130
     *
131
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
132
     *
133
     * @return \eZ\Publish\SPI\Persistence\Content\Location
134
     */
135
    public function loadByRemoteId($remoteId)
136
    {
137
        $data = $this->locationGateway->getBasicNodeDataByRemoteId($remoteId);
138
139
        return $this->locationMapper->createLocationFromRow($data);
140
    }
141
142
    /**
143
     * Loads all locations for $contentId, optionally limited to a sub tree
144
     * identified by $rootLocationId.
145
     *
146
     * @param int $contentId
147
     * @param int $rootLocationId
148
     *
149
     * @return \eZ\Publish\SPI\Persistence\Content\Location[]
150
     */
151
    public function loadLocationsByContent($contentId, $rootLocationId = null)
152
    {
153
        $rows = $this->locationGateway->loadLocationDataByContent($contentId, $rootLocationId);
154
155
        return $this->locationMapper->createLocationsFromRows($rows);
156
    }
157
158
    /**
159
     * @see \eZ\Publish\SPI\Persistence\Content\Location\Handler::loadParentLocationsForDraftContent
160
     */
161
    public function loadParentLocationsForDraftContent($contentId)
162
    {
163
        $rows = $this->locationGateway->loadParentLocationsDataForDraftContent($contentId);
164
165
        return $this->locationMapper->createLocationsFromRows($rows);
166
    }
167
168
    /**
169
     * Returns an array of default content states with content state group id as key.
170
     *
171
     * @return \eZ\Publish\SPI\Persistence\Content\ObjectState[]
172
     */
173
    protected function getDefaultContentStates()
174
    {
175
        $defaultObjectStatesMap = array();
176
177
        foreach ($this->objectStateHandler->loadAllGroups() as $objectStateGroup) {
178
            foreach ($this->objectStateHandler->loadObjectStates($objectStateGroup->id) as $objectState) {
179
                // Only register the first object state which is the default one.
180
                $defaultObjectStatesMap[$objectStateGroup->id] = $objectState;
181
                break;
182
            }
183
        }
184
185
        return $defaultObjectStatesMap;
186
    }
187
188
    /**
189
     * @param Content $content
190
     * @param \eZ\Publish\SPI\Persistence\Content\ObjectState[] $contentStates
191
     */
192
    protected function setContentStates(Content $content, array $contentStates)
193
    {
194
        foreach ($contentStates as $contentStateGroupId => $contentState) {
195
            $this->objectStateHandler->setContentState(
196
                $content->versionInfo->contentInfo->id,
197
                $contentStateGroupId,
198
                $contentState->id
199
            );
200
        }
201
    }
202
203
    /**
204
     * Copy location object identified by $sourceId, into destination identified by $destinationParentId.
205
     *
206
     * Performs a deep copy of the location identified by $sourceId and all of
207
     * its child locations, copying the most recent published content object
208
     * for each location to a new content object without any additional version
209
     * information. Relations are not copied. URLs are not touched at all.
210
     *
211
     * @todo Either move to async/batch or find ways toward optimizing away operations per object.
212
     * @todo Optionally retain dates and set creator
213
     *
214
     * @param mixed $sourceId
215
     * @param mixed $destinationParentId
216
     *
217
     * @return Location the newly created Location.
218
     */
219
    public function copySubtree($sourceId, $destinationParentId)
220
    {
221
        $children = $this->locationGateway->getSubtreeContent($sourceId);
222
        $destinationParentData = $this->locationGateway->getBasicNodeData($destinationParentId);
223
        $defaultObjectStates = $this->getDefaultContentStates();
224
        $contentMap = array();
225
        $locationMap = array(
226
            $children[0]['parent_node_id'] => array(
227
                'id' => $destinationParentId,
228
                'hidden' => (boolean)$destinationParentData['is_hidden'],
229
                'invisible' => (boolean)$destinationParentData['is_invisible'],
230
                'path_identification_string' => $destinationParentData['path_identification_string'],
231
            ),
232
        );
233
234
        $locations = array();
235
        foreach ($children as $child) {
236
            $locations[$child['contentobject_id']][$child['node_id']] = true;
237
        }
238
239
        $time = time();
240
        $mainLocations = array();
241
        $mainLocationsUpdate = array();
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
                );
249
250
                $this->setContentStates($content, $defaultObjectStates);
251
252
                $content = $this->contentHandler->publish(
253
                    $content->versionInfo->contentInfo->id,
254
                    $content->versionInfo->contentInfo->currentVersionNo,
255
                    new MetadataUpdateStruct(
256
                        array(
257
                            'publicationDate' => $time,
258
                            'modificationDate' => $time,
259
                        )
260
                    )
261
                );
262
263
                $contentMap[$child['contentobject_id']] = $content->versionInfo->contentInfo->id;
264
            }
265
266
            $createStruct = $this->locationMapper->getLocationCreateStruct($child);
267
            $createStruct->contentId = $contentMap[$child['contentobject_id']];
268
            $parentData = $locationMap[$child['parent_node_id']];
269
            $createStruct->parentId = $parentData['id'];
270
            $createStruct->invisible = $createStruct->hidden || $parentData['hidden'] || $parentData['invisible'];
271
            $pathString = explode('/', $child['path_identification_string']);
272
            $pathString = end($pathString);
273
            $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...
274
                ? $parentData['path_identification_string'] . '/' . $pathString
275
                : null;
276
277
            // Use content main location if already set, otherwise create location as main
278
            if (isset($mainLocations[$child['contentobject_id']])) {
279
                $createStruct->mainLocationId = $locationMap[$mainLocations[$child['contentobject_id']]]['id'];
280
            } else {
281
                $createStruct->mainLocationId = true;
282
                $mainLocations[$child['contentobject_id']] = $child['node_id'];
283
284
                // If needed mark for update
285
                if (
286
                    isset($locations[$child['contentobject_id']][$child['main_node_id']]) &&
287
                    count($locations[$child['contentobject_id']]) > 1 &&
288
                    $child['node_id'] !== $child['main_node_id']
289
                ) {
290
                    $mainLocationsUpdate[$child['contentobject_id']] = $child['main_node_id'];
291
                }
292
            }
293
294
            $newLocation = $this->create($createStruct);
295
296
            $locationMap[$child['node_id']] = array(
297
                'id' => $newLocation->id,
298
                'hidden' => $newLocation->hidden,
299
                'invisible' => $newLocation->invisible,
300
                '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...
301
            );
302
            if ($index === 0) {
303
                $copiedSubtreeRootLocation = $newLocation;
304
            }
305
        }
306
307
        // Update main locations
308
        foreach ($mainLocationsUpdate as $contentId => $mainLocationId) {
309
            $this->changeMainLocation(
310
                $contentMap[$contentId],
311
                $locationMap[$mainLocationId]['id']
312
            );
313
        }
314
315
        // If subtree root is main location for its content, update subtree section to the one of the
316
        // parent location content
317
        $subtreeRootContentInfo = $this->contentHandler->loadContentInfo($copiedSubtreeRootLocation->contentId);
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
        if ($subtreeRootContentInfo->mainLocationId === $copiedSubtreeRootLocation->id) {
319
            $this->setSectionForSubtree(
320
                $copiedSubtreeRootLocation->id,
321
                $this->contentHandler->loadContentInfo($this->load($destinationParentId)->contentId)->sectionId
322
            );
323
        }
324
325
        return $copiedSubtreeRootLocation;
326
    }
327
328
    /**
329
     * Moves location identified by $sourceId into new parent identified by $destinationParentId.
330
     *
331
     * Performs a full move of the location identified by $sourceId to a new
332
     * destination, identified by $destinationParentId. Relations do not need
333
     * to be updated, since they refer to Content. URLs are not touched.
334
     *
335
     * @param mixed $sourceId
336
     * @param mixed $destinationParentId
337
     *
338
     * @return bool
339
     */
340
    public function move($sourceId, $destinationParentId)
341
    {
342
        $sourceNodeData = $this->locationGateway->getBasicNodeData($sourceId);
343
        $destinationNodeData = $this->locationGateway->getBasicNodeData($destinationParentId);
344
345
        $this->locationGateway->moveSubtreeNodes(
346
            $sourceNodeData,
347
            $destinationNodeData
348
        );
349
350
        $this->locationGateway->updateNodeAssignment(
351
            $sourceNodeData['contentobject_id'],
352
            $sourceNodeData['parent_node_id'],
353
            $destinationParentId,
354
            Gateway::NODE_ASSIGNMENT_OP_CODE_MOVE
355
        );
356
    }
357
358
    /**
359
     * Marks the given nodes and all ancestors as modified.
360
     *
361
     * Optionally a time stamp with the modification date may be specified,
362
     * otherwise the current time is used.
363
     *
364
     * @param int|string $locationId
365
     * @param int $timestamp
366
     */
367
    public function markSubtreeModified($locationId, $timestamp = null)
368
    {
369
        $nodeData = $this->locationGateway->getBasicNodeData($locationId);
370
        $timestamp = $timestamp ?: time();
371
        $this->locationGateway->updateSubtreeModificationTime($nodeData['path_string'], $timestamp);
372
    }
373
374
    /**
375
     * Sets a location to be hidden, and it self + all children to invisible.
376
     *
377
     * @param mixed $id Location ID
378
     */
379
    public function hide($id)
380
    {
381
        $sourceNodeData = $this->locationGateway->getBasicNodeData($id);
382
383
        $this->locationGateway->hideSubtree($sourceNodeData['path_string']);
384
    }
385
386
    /**
387
     * Sets a location to be unhidden, and self + children to visible unless a parent is hiding the tree.
388
     * If not make sure only children down to first hidden node is marked visible.
389
     *
390
     * @param mixed $id
391
     */
392
    public function unHide($id)
393
    {
394
        $sourceNodeData = $this->locationGateway->getBasicNodeData($id);
395
396
        $this->locationGateway->unhideSubtree($sourceNodeData['path_string']);
397
    }
398
399
    /**
400
     * Swaps the content object being pointed to by a location object.
401
     *
402
     * Make the location identified by $locationId1 refer to the Content
403
     * referred to by $locationId2 and vice versa.
404
     *
405
     * @param mixed $locationId1
406
     * @param mixed $locationId2
407
     *
408
     * @return bool
409
     */
410
    public function swap($locationId1, $locationId2)
411
    {
412
        $this->locationGateway->swap($locationId1, $locationId2);
413
    }
414
415
    /**
416
     * Updates an existing location.
417
     *
418
     * @param \eZ\Publish\SPI\Persistence\Content\Location\UpdateStruct $location
419
     * @param int $locationId
420
     */
421
    public function update(UpdateStruct $location, $locationId)
422
    {
423
        $this->locationGateway->update($location, $locationId);
424
    }
425
426
    /**
427
     * Creates a new location rooted at $location->parentId.
428
     *
429
     * @param \eZ\Publish\SPI\Persistence\Content\Location\CreateStruct $createStruct
430
     *
431
     * @return \eZ\Publish\SPI\Persistence\Content\Location
432
     */
433
    public function create(CreateStruct $createStruct)
434
    {
435
        $parentNodeData = $this->locationGateway->getBasicNodeData($createStruct->parentId);
436
        $locationStruct = $this->locationGateway->create($createStruct, $parentNodeData);
437
        $this->locationGateway->createNodeAssignment(
438
            $createStruct,
439
            $parentNodeData['node_id'],
440
            LocationGateway::NODE_ASSIGNMENT_OP_CODE_CREATE_NOP
441
        );
442
443
        return $locationStruct;
444
    }
445
446
    /**
447
     * Removes all Locations under and including $locationId.
448
     *
449
     * Performs a recursive delete on the location identified by $locationId,
450
     * including all of its child locations. Content which is not referred to
451
     * by any other location is automatically removed. Content which looses its
452
     * main Location will get the first of its other Locations assigned as the
453
     * new main Location.
454
     *
455
     * @param mixed $locationId
456
     *
457
     * @return bool
458
     */
459
    public function removeSubtree($locationId)
460
    {
461
        $this->treeHandler->removeSubtree($locationId);
462
    }
463
464
    /**
465
     * Set section on all content objects in the subtree.
466
     *
467
     * @param mixed $locationId
468
     * @param mixed $sectionId
469
     */
470
    public function setSectionForSubtree($locationId, $sectionId)
471
    {
472
        $this->treeHandler->setSectionForSubtree($locationId, $sectionId);
473
    }
474
475
    /**
476
     * Changes main location of content identified by given $contentId to location identified by given $locationId.
477
     *
478
     * Updates ezcontentobject_tree and eznode_assignment tables (eznode_assignment for content current version number).
479
     *
480
     * @param mixed $contentId
481
     * @param mixed $locationId
482
     */
483
    public function changeMainLocation($contentId, $locationId)
484
    {
485
        $this->treeHandler->changeMainLocation($contentId, $locationId);
486
    }
487
}
488