Completed
Push — master ( 4484df...ecad36 )
by Gaetano
07:23
created

LocationManager::setReferences()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 9
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 9
loc 9
ccs 0
cts 7
cp 0
rs 9.6666
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 2
crap 6
1
<?php
2
3
namespace Kaliop\eZMigrationBundle\Core\Executor;
4
5
use eZ\Publish\API\Repository\Values\Content\Location;
6
use Kaliop\eZMigrationBundle\API\Collection\ContentCollection;
7
use Kaliop\eZMigrationBundle\API\Collection\LocationCollection;
8
use Kaliop\eZMigrationBundle\Core\Matcher\ContentMatcher;
9
use Kaliop\eZMigrationBundle\Core\Matcher\LocationMatcher;
10
use Kaliop\eZMigrationBundle\Core\Helper\SortConverter;
11
12
/**
13
 * Handles location migrations.
14
 */
15
class LocationManager extends RepositoryExecutor
16
{
17
    protected $supportedStepTypes = array('location');
18
    protected $supportedActions = array('create', 'load', 'update', 'delete', 'trash');
19
20
    protected $contentMatcher;
21
    protected $locationMatcher;
22
    protected $sortConverter;
23
24
    public function __construct(ContentMatcher $contentMatcher, LocationMatcher $locationMatcher, SortConverter $sortConverter)
25
    {
26
        $this->contentMatcher = $contentMatcher;
27
        $this->locationMatcher = $locationMatcher;
28
        $this->sortConverter = $sortConverter;
29
    }
30
31
    /**
32
     * Method to handle the create operation of the migration instructions
33
     */
34
    protected function create($step)
35
    {
36
        $locationService = $this->repository->getLocationService();
37
38
        if (!isset($step->dsl['parent_location']) && !isset($step->dsl['parent_location_id'])) {
39
            throw new \Exception('Missing parent location id. This is required to create the new location.');
40
        }
41
42
        // support legacy tag: parent_location_id
43
        if (!isset($step->dsl['parent_location']) && isset($step->dsl['parent_location_id'])) {
44
            $parentLocationIds = $step->dsl['parent_location_id'];
45
        } else {
46
            $parentLocationIds = $step->dsl['parent_location'];
47
        }
48
49
        if (!is_array($parentLocationIds)) {
50
            $parentLocationIds = array($parentLocationIds);
51
        }
52
53
        if (isset($step->dsl['is_main']) && count($parentLocationIds) > 1) {
54
            throw new \Exception('Can not set more than one new location as main.');
55
        }
56
57
        // resolve references and remote ids
58
        foreach ($parentLocationIds as $id => $parentLocationId) {
59
            $parentLocationId = $this->referenceResolver->resolveReference($parentLocationId);
60
            $parentLocationIds[$id] = $this->matchLocationByKey($parentLocationId)->id;
61
        }
62
63
        $contentCollection = $this->matchContents('create', $step);
64
65
        $locations = array();
66
        foreach ($contentCollection as $content) {
0 ignored issues
show
Bug introduced by
The expression $contentCollection of type object<Kaliop\eZMigratio...ContentCollection>|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
67
            $contentInfo = $content->contentInfo;
68
69
            foreach ($parentLocationIds as $parentLocationId) {
70
                $locationCreateStruct = $locationService->newLocationCreateStruct($parentLocationId);
71
72
                if (isset($step->dsl['is_hidden'])) {
73
                    $locationCreateStruct->hidden = $step->dsl['is_hidden'];
74
                }
75
76
                if (isset($step->dsl['priority'])) {
77
                    $locationCreateStruct->priority = $step->dsl['priority'];
78
                }
79
80
                if (isset($step->dsl['sort_order'])) {
81
                    $locationCreateStruct->sortOrder = $this->getSortOrder($step->dsl['sort_order']);
82
                }
83
84
                if (isset($step->dsl['sort_field'])) {
85
                    $locationCreateStruct->sortField = $this->getSortField($step->dsl['sort_field']);
86
                }
87
88
                $location = $locationService->createLocation($contentInfo, $locationCreateStruct);
89
90
                if (isset($step->dsl['is_main'])) {
91
                    $this->setMainLocation($location);
92
                }
93
94
                $locations[] = $location;
95
            }
96
        }
97
98
        $locationCollection = new LocationCollection($locations);
99
100
        $this->setReferences($locationCollection, $step);
0 ignored issues
show
Documentation introduced by
$locationCollection is of type object<Kaliop\eZMigratio...ion\LocationCollection>, but the function expects a object<Object>|object<Ka...ctCollectionCollection>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
101
102
        return $locationCollection;
103
    }
104
105
    protected function load($step)
106
    {
107
        $locationCollection = $this->matchLocations('load', $step);
108
109
        $this->setReferences($locationCollection, $step);
0 ignored issues
show
Documentation introduced by
$locationCollection is of type object<Kaliop\eZMigratio...ocationCollection>|null, but the function expects a object<Object>|object<Ka...ctCollectionCollection>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
110
111
        return $locationCollection;
112
    }
113
114
    /**
115
     * Updates information for a location like priority, sort field and sort order.
116
     * Updates the visibility of the location when needed.
117
     * Can move a location and its children to a new parent location or swap two locations.
118
     */
119
    protected function update($step)
120
    {
121
        $locationService = $this->repository->getLocationService();
122
123
        $locationCollection = $this->matchLocations('update', $step);
124
125
        if (count($locationCollection) > 1 && isset($step->dsl['references'])) {
126
            throw new \Exception("Can not execute Location update because multiple locations match, and a references section is specified in the dsl. References can be set when only 1 location matches");
127
        }
128
129
        if (count($locationCollection) > 1 && isset($step->dsl['swap_with_location'])) {
130
            throw new \Exception("Can not execute Location update because multiple locations match, and a swap_with_location is specified in the dsl.");
131
        }
132
133
        // support legacy tag: parent_location_id
134
        if (isset($step->dsl['swap_with_location']) && (isset($step->dsl['parent_location']) || isset($step->dsl['parent_location_id']))) {
135
            throw new \Exception('Cannot move location to a new parent and swap location with another location at the same time.');
136
        }
137
138
        foreach ($locationCollection as $key => $location) {
0 ignored issues
show
Bug introduced by
The expression $locationCollection of type object<Kaliop\eZMigratio...ocationCollection>|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
139
140
            if (isset($step->dsl['priority'])
141
                || isset($step->dsl['sort_field'])
142
                || isset($step->dsl['sort_order'])
143
                || isset($step->dsl['remote_id'])
144
            ) {
145
                $locationUpdateStruct = $locationService->newLocationUpdateStruct();
146
147
                if (isset($step->dsl['priority'])) {
148
                    $locationUpdateStruct->priority = $step->dsl['priority'];
149
                }
150
151
                if (isset($step->dsl['sort_field'])) {
152
                    $locationUpdateStruct->sortField = $this->getSortField($step->dsl['sort_field'], $location->sortField);
153
                }
154
155
                if (isset($step->dsl['sort_order'])) {
156
                    $locationUpdateStruct->sortOrder = $this->getSortOrder($step->dsl['sort_order'], $location->sortOrder);
157
                }
158
159
                if (isset($step->dsl['remote_id'])) {
160
                    $locationUpdateStruct->remoteId = $step->dsl['remote_id'];
161
                }
162
163
                $location = $locationService->updateLocation($location, $locationUpdateStruct);
164
            }
165
166
            // Check if visibility needs to be updated
167
            if (isset($step->dsl['is_hidden'])) {
168
                if ($step->dsl['is_hidden']) {
169
                    $location = $locationService->hideLocation($location);
170
                } else {
171
                    $location = $locationService->unhideLocation($location);
172
                }
173
            }
174
175
            // Move or swap location
176
            if (isset($step->dsl['parent_location']) || isset($step->dsl['parent_location_id'])) {
177
                // Move the location and all its children to a new parent
178
                $parentLocationId = isset($step->dsl['parent_location']) ? $step->dsl['parent_location'] : $step->dsl['parent_location_id'];
179
                $parentLocationId = $this->referenceResolver->resolveReference($parentLocationId);
180
181
                $newParentLocation = $locationService->loadLocation($parentLocationId);
182
183
                $locationService->moveSubtree($location, $newParentLocation);
184
            } elseif (isset($step->dsl['swap_with_location'])) {
185
                // Swap locations
186
                $swapLocationId = $step->dsl['swap_with_location'];
187
                $swapLocationId = $this->referenceResolver->resolveReference($swapLocationId);
188
189
                $locationToSwap = $this->matchLocationByKey($swapLocationId);
190
191
                $locationService->swapLocation($location, $locationToSwap);
192
            }
193
194
            // make the location the main one
195
            if (isset($step->dsl['is_main'])) {
196
                $this->setMainLocation($location);
197
            }
198
199
            $locationCollection[$key] = $location;
200
        }
201
202
        $this->setReferences($locationCollection, $step);
0 ignored issues
show
Documentation introduced by
$locationCollection is of type object<Kaliop\eZMigratio...ocationCollection>|null, but the function expects a object<Object>|object<Ka...ctCollectionCollection>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
203
204
        return $locationCollection;
205
    }
206
207
    /**
208
     * Delete locations
209
     */
210 View Code Duplication
    protected function delete($step)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
211
    {
212
        $locationCollection = $this->matchLocations('delete', $step);
213
214
        $this->setReferences($locationCollection, $step);
0 ignored issues
show
Documentation introduced by
$locationCollection is of type object<Kaliop\eZMigratio...ocationCollection>|null, but the function expects a object<Object>|object<Ka...ctCollectionCollection>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
215
216
        $locationService = $this->repository->getLocationService();
217
218
        foreach ($locationCollection as $location) {
0 ignored issues
show
Bug introduced by
The expression $locationCollection of type object<Kaliop\eZMigratio...ocationCollection>|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
219
            $locationService->deleteLocation($location);
220
        }
221
222
        return $locationCollection;
223
    }
224
225
    /**
226
     * Delete locations sending them to the trash
227
     */
228 View Code Duplication
    protected function trash($step)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
229
    {
230
        $locationCollection = $this->matchLocations('delete', $step);
231
232
        $this->setReferences($locationCollection, $step);
0 ignored issues
show
Documentation introduced by
$locationCollection is of type object<Kaliop\eZMigratio...ocationCollection>|null, but the function expects a object<Object>|object<Ka...ctCollectionCollection>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
233
234
        $trashService = $this->repository->getTrashService();
235
236
        foreach ($locationCollection as $location) {
0 ignored issues
show
Bug introduced by
The expression $locationCollection of type object<Kaliop\eZMigratio...ocationCollection>|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
237
            $trashService->trash($location);
238
        }
239
240
        return $locationCollection;
241
    }
242
243
    /**
244
     * @param string $action
245
     * @return LocationCollection
246
     * @throws \Exception
247
     */
248 View Code Duplication
    protected function matchLocations($action, $step)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
249
    {
250
        if (!isset($step->dsl['location_id']) && !isset($step->dsl['match'])) {
251
            throw new \Exception("The id or a match condition is required to $action a location");
252
        }
253
254
        // Backwards compat
255
        if (isset($step->dsl['match'])) {
256
            $match = $step->dsl['match'];
257
        } else {
258
            $match = array('location_id' => $step->dsl['location_id']);
259
        }
260
261
        // convert the references passed in the match
262
        $match = $this->resolveReferencesRecursively($match);
263
264
        return $this->locationMatcher->match($match);
265
    }
266
267
    /**
268
     * @param Location $location
269
     * @param array $references the definitions of the references to set
270
     * @throws \InvalidArgumentException When trying to assign a reference to an unsupported attribute
271
     * @return array key: the reference names, values: the reference values
272
     */
273
    protected function getReferencesValues($location, array $references)
274
    {
275
        $refs = array();
276
277
        foreach ($references as $reference) {
278
            switch ($reference['attribute']) {
279
                case 'location_id':
280
                case 'id':
281
                    $value = $location->id;
282
                    break;
283
                case 'remote_id':
284
                case 'location_remote_id':
285
                    $value = $location->remoteId;
286
                    break;
287
                case 'always_available':
288
                    $value = $location->contentInfo->alwaysAvailable;
289
                    break;
290
                case 'content_id':
291
                    $value = $location->contentId;
292
                    break;
293
                case 'content_type_id':
294
                    $value = $location->contentInfo->contentTypeId;
295
                    break;
296
                case 'content_type_identifier':
297
                    $contentTypeService = $this->repository->getContentTypeService();
298
                    $value = $contentTypeService->loadContentType($location->contentInfo->contentTypeId)->identifier;
299
                    break;
300
                case 'current_version':
301
                case 'current_version_no':
302
                    $value = $location->contentInfo->currentVersionNo;
303
                    break;
304
                case 'depth':
305
                    $value = $location->depth;
306
                    break;
307
                case 'is_hidden':
308
                    $value = $location->hidden;
309
                    break;
310
                case 'main_location_id':
311
                    $value = $location->contentInfo->mainLocationId;
312
                    break;
313
                case 'main_language_code':
314
                    $value = $location->contentInfo->mainLanguageCode;
315
                    break;
316
                case 'modification_date':
317
                    $value = $location->contentInfo->modificationDate->getTimestamp();
318
                    break;
319
                case 'name':
320
                    $value = $location->contentInfo->name;
321
                    break;
322
                case 'owner_id':
323
                    $value = $location->contentInfo->ownerId;
324
                    break;
325
                case 'parent_location_id':
326
                    $value = $location->parentLocationId;
327
                    break;
328
                case 'path':
329
                    $value = $location->pathString;
330
                    break;
331
                case 'priority':
332
                    $value = $location->priority;
333
                    break;
334
                case 'publication_date':
335
                    $value = $location->contentInfo->publishedDate->getTimestamp();
336
                    break;
337
                case 'section_id':
338
                    $value = $location->contentInfo->sectionId;
339
                    break;
340
                case 'section_identifier':
341
                    $sectionService = $this->repository->getSectionService();
342
                    $value = $sectionService->loadSection($location->contentInfo->sectionId)->identifier;
343
                    break;
344
                case 'sort_field':
345
                    $value = $this->sortConverter->sortField2Hash($location->sortField);
346
                    break;
347
                case 'sort_order':
348
                    $value = $this->sortConverter->sortOrder2Hash($location->sortOrder);
349
                    break;
350
                default:
351
                    throw new \InvalidArgumentException('Location Manager does not support setting references for attribute ' . $reference['attribute']);
352
            }
353
354
            $refs[$reference['identifier']] = $value;
355
        }
356
357
        return $refs;
358
    }
359
360
    /**
361
     * @param int|string|array $locationKey
362
     * @return Location
363
     */
364
    public function matchLocationByKey($locationKey)
365
    {
366
        return $this->locationMatcher->matchOneByKey($locationKey);
367
    }
368
369
    /**
370
     * NB: weirdly enough, it returns contents, not locations
371
     *
372
     * @param string $action
373
     * @return ContentCollection
374
     * @throws \Exception
375
     */
376
    protected function matchContents($action, $step)
377
    {
378
        if (!isset($step->dsl['object_id']) && !isset($step->dsl['remote_id']) && !isset($step->dsl['match'])) {
379
            throw new \Exception("The ID or remote ID of an object or a Match Condition is required to $action a new location.");
380
        }
381
382
        // Backwards compat
383
        if (!isset($step->dsl['match'])) {
384
            if (isset($step->dsl['object_id'])) {
385
                $step->dsl['match'] = array('content_id' => $step->dsl['object_id']);
386
            } elseif (isset($step->dsl['remote_id'])) {
387
                $step->dsl['match'] = array('content_remote_id' => $step->dsl['remote_id']);
388
            }
389
        }
390
391
        $match = $step->dsl['match'];
392
393
        // convert the references passed in the match
394
        foreach ($match as $condition => $values) {
395
            if (is_array($values)) {
396
                foreach ($values as $position => $value) {
397
                    $match[$condition][$position] = $this->referenceResolver->resolveReference($value);
398
                }
399
            } else {
400
                $match[$condition] = $this->referenceResolver->resolveReference($values);
401
            }
402
        }
403
404
        return $this->contentMatcher->matchContent($match);
405
    }
406
407
    protected function setMainLocation(Location $location)
408
    {
409
        $contentService = $this->repository->getContentService();
410
        $contentMetaDataUpdateStruct = $contentService->newContentMetadataUpdateStruct();
411
        $contentMetaDataUpdateStruct->mainLocationId = $location->id;
412
        $contentService->updateContentMetadata($location->contentInfo, $contentMetaDataUpdateStruct);
413
    }
414
415
    /**
416
     * @param $newValue
417
     * @param null $currentValue
418
     * @return int|null
419
     *
420
     * * @todo make protected
421
     */
422
    public function getSortField($newValue, $currentValue = null)
423
    {
424
        $sortField = $currentValue;
425
426
        if ($newValue !== null) {
427
            $sortField = $this->sortConverter->hash2SortField($newValue);
428
        }
429
430
        return $sortField;
431
    }
432
433
    /**
434
     * Get the sort order based on the current value and the value in the DSL definition.
435
     *
436
     * @see \eZ\Publish\API\Repository\Values\Content\Location::SORT_ORDER_*
437
     *
438
     * @param int $newValue
439
     * @param int $currentValue
440
     * @return int|null
441
     *
442
     * @todo make protected
443
     */
444
    public function getSortOrder($newValue, $currentValue = null)
445
    {
446
        $sortOrder = $currentValue;
447
448
        if ($newValue !== null) {
449
            $sortOrder = $this->sortConverter->hash2SortOrder($newValue);
450
        }
451
452
        return $sortOrder;
453
    }
454
455
}
456