Completed
Branch master (e35419)
by Gaetano
06:40
created

ContentManager::getReferencesValues()   D

Complexity

Conditions 29
Paths 28

Size

Total Lines 108
Code Lines 88

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 82
CRAP Score 29.1598

Importance

Changes 0
Metric Value
cc 29
eloc 88
nc 28
nop 3
dl 0
loc 108
ccs 82
cts 87
cp 0.9425
crap 29.1598
rs 4.1666
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
namespace Kaliop\eZMigrationBundle\Core\Executor;
4
5
use eZ\Publish\API\Repository\Values\ContentType\ContentType;
6
use eZ\Publish\API\Repository\Values\ContentType\FieldDefinition;
7
use eZ\Publish\API\Repository\Values\Content\Location;
8
use eZ\Publish\API\Repository\Values\Content\Content;
9
use eZ\Publish\API\Repository\Values\Content\ContentCreateStruct;
10
use eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct;
11
use eZ\Publish\Core\Base\Exceptions\NotFoundException;
12
use Kaliop\eZMigrationBundle\API\Collection\ContentCollection;
13
use Kaliop\eZMigrationBundle\API\MigrationGeneratorInterface;
14
use Kaliop\eZMigrationBundle\Core\FieldHandlerManager;
15
use Kaliop\eZMigrationBundle\Core\Matcher\ContentMatcher;
16
use Kaliop\eZMigrationBundle\Core\Matcher\SectionMatcher;
17
use Kaliop\eZMigrationBundle\Core\Matcher\UserMatcher;
18
use Kaliop\eZMigrationBundle\Core\Matcher\ObjectStateMatcher;
19
use Kaliop\eZMigrationBundle\Core\Matcher\ObjectStateGroupMatcher;
20
use Kaliop\eZMigrationBundle\Core\Helper\SortConverter;
21
use JmesPath\Env as JmesPath;
22
23
/**
24
 * Handles content migrations.
25
 *
26
 * @todo add support for updating of content metadata
27
 */
28
class ContentManager extends RepositoryExecutor implements MigrationGeneratorInterface
29
{
30
    protected $supportedStepTypes = array('content');
31
    protected $supportedActions = array('create', 'load', 'update', 'delete');
32
33
    protected $contentMatcher;
34
    protected $sectionMatcher;
35
    protected $userMatcher;
36
    protected $objectStateMatcher;
37
    protected $objectStateGroupMatcher;
38
    protected $fieldHandlerManager;
39
    protected $locationManager;
40
    protected $sortConverter;
41
42
    // these are not exported when generating a migration
43
    protected $ignoredStateGroupIdentifiers = array('ez_lock');
44
45 80
    public function __construct(
46
        ContentMatcher $contentMatcher,
47
        SectionMatcher $sectionMatcher,
48
        UserMatcher $userMatcher,
49
        ObjectStateMatcher $objectStateMatcher,
50
        ObjectStateGroupMatcher $objectStateGroupMatcher,
51
        FieldHandlerManager $fieldHandlerManager,
52
        LocationManager $locationManager,
53
        SortConverter $sortConverter
54
    ) {
55 80
        $this->contentMatcher = $contentMatcher;
56 80
        $this->sectionMatcher = $sectionMatcher;
57 80
        $this->userMatcher = $userMatcher;
58 80
        $this->objectStateMatcher = $objectStateMatcher;
59 80
        $this->objectStateGroupMatcher = $objectStateGroupMatcher;
60 80
        $this->fieldHandlerManager = $fieldHandlerManager;
61 80
        $this->locationManager = $locationManager;
62 80
        $this->sortConverter = $sortConverter;
63 80
    }
64
65
    /**
66
     * Handles the content create migration action type
67
     */
68 14
    protected function create($step)
69
    {
70 14
        $contentService = $this->repository->getContentService();
71 14
        $locationService = $this->repository->getLocationService();
72 14
        $contentTypeService = $this->repository->getContentTypeService();
73
74 14
        $contentTypeIdentifier = $step->dsl['content_type'];
75 14
        $contentTypeIdentifier = $this->referenceResolver->resolveReference($contentTypeIdentifier);
76
        /// @todo use a contenttypematcher
77 14
        $contentType = $contentTypeService->loadContentTypeByIdentifier($contentTypeIdentifier);
78
79 14
        $contentCreateStruct = $contentService->newContentCreateStruct($contentType, $this->getLanguageCode($step));
80
81 14
        $this->setFields($contentCreateStruct, $step->dsl['attributes'], $contentType, $step);
82
83 12
        if (isset($step->dsl['always_available'])) {
84
            $contentCreateStruct->alwaysAvailable = $step->dsl['always_available'];
85
        } else {
86
            // Could be removed when https://github.com/ezsystems/ezpublish-kernel/pull/1874 is merged,
87
            // but we strive to support old eZ kernel versions as well...
88 12
            $contentCreateStruct->alwaysAvailable = $contentType->defaultAlwaysAvailable;
89
        }
90
91 12
        if (isset($step->dsl['remote_id'])) {
92 3
            $contentCreateStruct->remoteId = $step->dsl['remote_id'];
93
        }
94
95 12
        if (isset($step->dsl['section'])) {
96 1
            $sectionKey = $this->referenceResolver->resolveReference($step->dsl['section']);
97 1
            $section = $this->sectionMatcher->matchOneByKey($sectionKey);
98 1
            $contentCreateStruct->sectionId = $section->id;
99
        }
100
101 12
        if (isset($step->dsl['owner'])) {
102 2
            $owner = $this->getUser($step->dsl['owner']);
103 2
            $contentCreateStruct->ownerId = $owner->id;
104
        }
105
106
        // This is a bit tricky, as the eZPublish API does not support having a different creator and owner with only 1 version.
107
        // We allow it, hoping that nothing gets broken because of it
108 12
        if (isset($step->dsl['version_creator'])) {
109 1
            $realContentOwnerId = $contentCreateStruct->ownerId;
110 1
            if ($realContentOwnerId == null) {
111 1
                $realContentOwnerId = $this->repository->getCurrentUser()->id;
112
            }
113 1
            $versionCreator = $this->getUser($step->dsl['version_creator']);
114 1
            $contentCreateStruct->ownerId = $versionCreator->id;
115
        }
116
117 12
        if (isset($step->dsl['modification_date'])) {
118 1
            $contentCreateStruct->modificationDate = $this->toDateTime($step->dsl['modification_date']);
119
        }
120
121
        // instantiate a location create struct from the parent location:
122
        // BC
123 12
        $locationId = isset($step->dsl['parent_location']) ? $step->dsl['parent_location'] : (
124 12
            isset($step->dsl['main_location']) ? $step->dsl['main_location'] : null
125
        );
126
        // 1st resolve references
127 12
        $locationId = $this->referenceResolver->resolveReference($locationId);
128
        // 2nd allow to specify the location via remote_id
129 12
        $locationId = $this->locationManager->matchLocationByKey($locationId)->id;
130 12
        $locationCreateStruct = $locationService->newLocationCreateStruct($locationId);
131
132 12
        if (isset($step->dsl['location_remote_id'])) {
133 2
            $locationCreateStruct->remoteId = $step->dsl['location_remote_id'];
134
        }
135
136 12
        if (isset($step->dsl['priority'])) {
137 1
            $locationCreateStruct->priority = $step->dsl['priority'];
138
        }
139
140 12
        if (isset($step->dsl['is_hidden'])) {
141 1
            $locationCreateStruct->hidden = $step->dsl['is_hidden'];
142
        }
143
144 12
        if (isset($step->dsl['sort_field'])) {
145 1
            $locationCreateStruct->sortField = $this->sortConverter->hash2SortField($step->dsl['sort_field']);
146
        } else {
147 12
            $locationCreateStruct->sortField = $contentType->defaultSortField;
148
        }
149
150 12
        if (isset($step->dsl['sort_order'])) {
151 1
            $locationCreateStruct->sortOrder = $this->sortConverter->hash2SortOrder($step->dsl['sort_order']);
152
        } else {
153 12
            $locationCreateStruct->sortOrder = $contentType->defaultSortOrder;
154
        }
155
156 12
        $locations = array($locationCreateStruct);
157
158
        // BC
159 12
        $other_locations = isset($step->dsl['other_parent_locations']) ? $step->dsl['other_parent_locations'] : (
160 12
            isset($step->dsl['other_locations']) ? $step->dsl['other_locations'] : null
161
        );
162 12
        if (isset($other_locations)) {
163
            foreach ($other_locations as $locationId) {
164
                $locationId = $this->referenceResolver->resolveReference($locationId);
165
                $locationId = $this->locationManager->matchLocationByKey($locationId)->id;
166
                $secondaryLocationCreateStruct = $locationService->newLocationCreateStruct($locationId);
167
                array_push($locations, $secondaryLocationCreateStruct);
168
            }
169
        }
170
171
        // create a draft using the content and location create struct and publish it
172 12
        $draft = $contentService->createContent($contentCreateStruct, $locations);
173 10
        $content = $contentService->publishVersion($draft->versionInfo);
174
175 10
        if (isset($step->dsl['object_states'])) {
176 2
            $this->setObjectStates($content, $step->dsl['object_states']);
177
        }
178
179
        // 2nd part of the hack: re-set the content owner to its intended value
180 10
        if (isset($step->dsl['version_creator']) || isset($step->dsl['publication_date'])) {
181 1
            $contentMetaDataUpdateStruct = $contentService->newContentMetadataUpdateStruct();
182
183 1
            if (isset($step->dsl['version_creator'])) {
184 1
                $contentMetaDataUpdateStruct->ownerId = $realContentOwnerId;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $realContentOwnerId does not seem to be defined for all execution paths leading up to this point.
Loading history...
185
            }
186 1
            if (isset($step->dsl['publication_date'])) {
187 1
                $contentMetaDataUpdateStruct->publishedDate = $this->toDateTime($step->dsl['publication_date']);
188
            }
189
            // we have to do this to make sure we preserve the custom modification date
190 1
            if (isset($step->dsl['modification_date'])) {
191 1
                $contentMetaDataUpdateStruct->modificationDate = $this->toDateTime($step->dsl['modification_date']);
192
            }
193
194 1
            $contentService->updateContentMetadata($content->contentInfo, $contentMetaDataUpdateStruct);
195
        }
196
197 10
        $this->setReferences($content, $step);
198
199 10
        return $content;
200
    }
201
202 6
    protected function load($step)
203
    {
204 6
        $contentCollection = $this->matchContents('load', $step);
205
206 6
        $this->setReferences($contentCollection, $step);
207
208 5
        return $contentCollection;
209
    }
210
211
    /**
212
     * Handles the content update migration action type
213
     *
214
     * @todo handle updating of more metadata fields
215
     */
216 9
    protected function update($step)
217
    {
218 9
        $contentService = $this->repository->getContentService();
219 9
        $contentTypeService = $this->repository->getContentTypeService();
220
221 9
        $contentCollection = $this->matchContents('update', $step);
222
223 9
        if (count($contentCollection) > 1 && isset($step->dsl['references'])) {
224
            throw new \Exception("Can not execute Content update because multiple contents match, and a references section is specified in the dsl. References can be set when only 1 content matches");
225
        }
226
227 9
        if (count($contentCollection) > 1 && isset($step->dsl['main_location'])) {
228
            throw new \Exception("Can not execute Content update because multiple contents match, and a main_location section is specified in the dsl. References can be set when only 1 content matches");
229
        }
230
231 9
        $contentType = array();
232
233 9
        foreach ($contentCollection as $key => $content) {
234 9
            $contentInfo = $content->contentInfo;
235
236 9
            if (!isset($contentType[$contentInfo->contentTypeId])) {
237 9
                $contentType[$contentInfo->contentTypeId] = $contentTypeService->loadContentType($contentInfo->contentTypeId);
238
            }
239
240 9
            if (isset($step->dsl['attributes']) || isset($step->dsl['version_creator'])) {
241 4
                $contentUpdateStruct = $contentService->newContentUpdateStruct();
242
243 4
                if (isset($step->dsl['attributes'])) {
244 4
                    $this->setFields($contentUpdateStruct, $step->dsl['attributes'], $contentType[$contentInfo->contentTypeId], $step);
245
                }
246
247 4
                $versionCreator = null;
248 4
                if (isset($step->dsl['version_creator'])) {
249 1
                    $versionCreator = $this->getUser($step->dsl['version_creator']);
250
                }
251
252 4
                $draft = $contentService->createContentDraft($contentInfo, null, $versionCreator);
253 4
                $contentService->updateContent($draft->versionInfo, $contentUpdateStruct);
254 4
                $content = $contentService->publishVersion($draft->versionInfo);
255
            }
256
257 9
            if (isset($step->dsl['always_available']) ||
258 9
                isset($step->dsl['new_remote_id']) ||
259 8
                isset($step->dsl['owner']) ||
260 7
                isset($step->dsl['modification_date']) ||
261 9
                isset($step->dsl['publication_date'])) {
262
263 2
                $contentMetaDataUpdateStruct = $contentService->newContentMetadataUpdateStruct();
264
265 2
                if (isset($step->dsl['always_available'])) {
266
                    $contentMetaDataUpdateStruct->alwaysAvailable = $step->dsl['always_available'];
267
                }
268
269 2
                if (isset($step->dsl['new_remote_id'])) {
270 1
                    $contentMetaDataUpdateStruct->remoteId = $step->dsl['new_remote_id'];
271
                }
272
273 2
                if (isset($step->dsl['owner'])) {
274 2
                    $owner = $this->getUser($step->dsl['owner']);
275 2
                    $contentMetaDataUpdateStruct->ownerId = $owner->id;
276
                }
277
278 2
                if (isset($step->dsl['modification_date'])) {
279 1
                    $contentMetaDataUpdateStruct->modificationDate = $this->toDateTime($step->dsl['modification_date']);
280
                }
281
282 2
                if (isset($step->dsl['publication_date'])) {
283 1
                    $contentMetaDataUpdateStruct->publishedDate = $this->toDateTime($step->dsl['publication_date']);
284
                }
285
286 2
                $content = $contentService->updateContentMetadata($content->contentInfo, $contentMetaDataUpdateStruct);
287
            }
288
289 9
            if (isset($step->dsl['section'])) {
290 1
                $this->setSection($content, $step->dsl['section']);
291
            }
292
293 9
            if (isset($step->dsl['object_states'])) {
294 1
                $this->setObjectStates($content, $step->dsl['object_states']);
295
            }
296
297 9
            if (isset($step->dsl['main_location'])) {
298 1
                $this->setMainLocation($content, $step->dsl['main_location']);
299
300
            }
301 9
            $contentCollection[$key] = $content;
302
        }
303
304 9
        $this->setReferences($contentCollection, $step);
305
306 9
        return $contentCollection;
307
    }
308
309
    /**
310
     * Handles the content delete migration action type
311
     */
312 9
    protected function delete($step)
313
    {
314 9
        $contentCollection = $this->matchContents('delete', $step);
315
316 9
        $this->setReferences($contentCollection, $step);
317
318 9
        $contentService = $this->repository->getContentService();
319
320 9
        foreach ($contentCollection as $content) {
321
            try {
322 9
                $contentService->deleteContent($content->contentInfo);
323 9
            } catch (NotFoundException $e) {
324
                // Someone else (or even us, by virtue of location tree?) removed the content which we found just a
325
                // second ago. We can safely ignore this
326
            }
327
        }
328
329 9
        return $contentCollection;
330
    }
331
332
    /**
333
     * @param string $action
334
     * @return ContentCollection
335
     * @throws \Exception
336
     */
337 13
    protected function matchContents($action, $step)
338
    {
339 13
        if (!isset($step->dsl['object_id']) && !isset($step->dsl['remote_id']) && !isset($step->dsl['match'])) {
340
            throw new \Exception("The id or remote id of an object or a match condition is required to $action a content");
341
        }
342
343
        // Backwards compat
344
345 13
        if (isset($step->dsl['match'])) {
346 13
            $match = $step->dsl['match'];
347
        } else {
348 1
            if (isset($step->dsl['object_id'])) {
349 1
                $match = array('content_id' => $step->dsl['object_id']);
350
            } elseif (isset($step->dsl['remote_id'])) {
351
                $match = array('content_remote_id' => $step->dsl['remote_id']);
352
            }
353
        }
354
355
        // convert the references passed in the match
356 13
        $match = $this->resolveReferencesRecursively($match);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $match does not seem to be defined for all execution paths leading up to this point.
Loading history...
357
358 13
        $offset = isset($step->dsl['match_offset']) ? $this->referenceResolver->resolveReference($step->dsl['match_offset']) : 0;
359 13
        $limit = isset($step->dsl['match_limit']) ? $this->referenceResolver->resolveReference($step->dsl['match_limit']) : 0;
360 13
        $sort = isset($step->dsl['match_sort']) ? $this->referenceResolver->resolveReference($step->dsl['match_sort']) : array();
361
362 13
        return $this->contentMatcher->match($match, $sort, $offset, $limit);
363
    }
364
365
    /**
366
     * @param Content $content
367
     * @param array $references the definitions of the references to set
368
     * @throws \InvalidArgumentException When trying to assign a reference to an unsupported attribute
369
     * @return array key: the reference names, values: the reference values
370
     */
371 9
    protected function getReferencesValues($content, array $references, $step)
372
    {
373 9
        $refs = array();
374
375 9
        foreach ($references as $reference) {
376
377 9
            switch ($reference['attribute']) {
378 9
                case 'object_id':
379 9
                case 'content_id':
380 9
                case 'id':
381 9
                    $value = $content->id;
382 9
                    break;
383 7
                case 'remote_id':
384 7
                case 'content_remote_id':
385 2
                    $value = $content->contentInfo->remoteId;
386 2
                    break;
387 7
                case 'always_available':
388 1
                    $value = $content->contentInfo->alwaysAvailable;
389 1
                    break;
390 7
                case 'content_type_id':
391 1
                    $value = $content->contentInfo->contentTypeId;
392 1
                    break;
393 7
                case 'content_type_identifier':
394 1
                    $contentTypeService = $this->repository->getContentTypeService();
395 1
                    $value = $contentTypeService->loadContentType($content->contentInfo->contentTypeId)->identifier;
396 1
                    break;
397 7
                case 'current_version':
398 7
                case 'current_version_no':
399 1
                    $value = $content->contentInfo->currentVersionNo;
400 1
                    break;
401 7
                case 'location_id':
402 5
                case 'main_location_id':
403 3
                    $value = $content->contentInfo->mainLocationId;
404 3
                    break;
405 5
                case 'main_language_code':
406 1
                    $value = $content->contentInfo->mainLanguageCode;
407 1
                    break;
408 5
                case 'modification_date':
409 1
                    $value = $content->contentInfo->modificationDate->getTimestamp();
410 1
                    break;
411 5
                case 'name':
412 1
                    $value = $content->contentInfo->name;
413 1
                    break;
414 5
                case 'owner_id':
415 1
                    $value = $content->contentInfo->ownerId;
416 1
                    break;
417 5
                case 'path':
418 2
                    $locationService = $this->repository->getLocationService();
419 2
                    $value = $locationService->loadLocation($content->contentInfo->mainLocationId)->pathString;
420 2
                    break;
421 4
                case 'publication_date':
422 1
                    $value = $content->contentInfo->publishedDate->getTimestamp();
423 1
                    break;
424 4
                case 'section_id':
425 1
                    $value = $content->contentInfo->sectionId;
426 1
                    break;
427 4
                case 'section_identifier':
428 1
                    $sectionService = $this->repository->getSectionService();
429 1
                    $value = $sectionService->loadSection($content->contentInfo->sectionId)->identifier;
430 1
                    break;
431 3
                case 'version_count':
432 1
                    $contentService = $this->repository->getContentService();
433 1
                    $value = count($contentService->loadVersions($content->contentInfo));
434 1
                    break;
435
                default:
436 2
                    if (strpos($reference['attribute'], 'object_state.') === 0) {
437 1
                        $stateGroupKey = substr($reference['attribute'], 13);
438 1
                        $stateGroup = $this->objectStateGroupMatcher->matchOneByKey($stateGroupKey);
439 1
                        $value = $stateGroupKey . '/' . $this->repository->getObjectStateService()->
440 1
                            getContentState($content->contentInfo, $stateGroup)->identifier;
441 1
                        break;
442
                    }
443
444
                    // allow to get the value of fields as well as their sub-parts
445 1
                    if (strpos($reference['attribute'], 'attributes.') === 0) {
446 1
                        $contentType = $this->repository->getContentTypeService()->loadContentType(
447 1
                            $content->contentInfo->contentTypeId
448
                        );
449 1
                        $parts = explode('.', $reference['attribute']);
450
                        // totally not sure if this list of special chars is correct for what could follow a jmespath identifier...
451
                        // also what about quoted strings?
452 1
                        $fieldIdentifier = preg_replace('/[[(|&!{].*$/', '', $parts[1]);
453 1
                        $field = $content->getField($fieldIdentifier);
0 ignored issues
show
Bug introduced by
The method getField() does not exist on eZ\Publish\API\Repository\Values\Content\Content. Did you maybe mean getFieldValue()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

453
                        /** @scrutinizer ignore-call */ 
454
                        $field = $content->getField($fieldIdentifier);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
454 1
                        $fieldDefinition = $contentType->getFieldDefinition($fieldIdentifier);
455 1
                        $hashValue = $this->fieldHandlerManager->fieldValueToHash(
456 1
                            $fieldDefinition->fieldTypeIdentifier, $contentType->identifier, $field->value
457
                        );
458 1
                        if (is_array($hashValue)) {
459 1
                            if (count($parts) == 2 && $fieldIdentifier === $parts[1]) {
460
                                throw new \InvalidArgumentException('Content Manager does not support setting references for attribute ' . $reference['attribute'] . ': the given attribute has an array value');
461
                            }
462 1
                            $value = JmesPath::search(implode('.', array_slice($parts, 1)), array($fieldIdentifier => $hashValue));
463
                        } else {
464
                            if (count($parts) > 2) {
465
                                throw new \InvalidArgumentException('Content Manager does not support setting references for attribute ' . $reference['attribute'] . ': the given attribute has a scalar value');
466
                            }
467
                            $value = $hashValue;
468
                        }
469 1
                        break;
470
                    }
471
472
                    throw new \InvalidArgumentException('Content Manager does not support setting references for attribute ' . $reference['attribute']);
473
            }
474
475 9
            $refs[$reference['identifier']] = $value;
476
        }
477
478 9
        return $refs;
479
    }
480
481
    /**
482
     * @param array $matchCondition
483
     * @param string $mode
484
     * @param array $context
485
     * @throws \Exception
486
     * @return array
487
     *
488
     * @todo add support for dumping all object languages
489
     * @todo add 2ndary locations when in 'update' mode
490
     * @todo add dumping of sort_field and sort_order for 2ndary locations
491
     */
492 3
    public function generateMigration(array $matchCondition, $mode, array $context = array())
493
    {
494 3
        $previousUserId = $this->loginUser($this->getAdminUserIdentifierFromContext($context));
495 3
        $contentCollection = $this->contentMatcher->match($matchCondition);
496 3
        $data = array();
497
498
        /** @var \eZ\Publish\API\Repository\Values\Content\Content $content */
499 3
        foreach ($contentCollection as $content) {
500
501 3
            $location = $this->repository->getLocationService()->loadLocation($content->contentInfo->mainLocationId);
502 3
            $contentType = $this->repository->getContentTypeService()->loadContentType(
503 3
                $content->contentInfo->contentTypeId
504
            );
505
506
            $contentData = array(
507 3
                'type' => reset($this->supportedStepTypes),
508 3
                'mode' => $mode
509
            );
510
511
            switch ($mode) {
512 3
                case 'create':
513 1
                    $contentData = array_merge(
514 1
                        $contentData,
515
                        array(
516 1
                            'content_type' => $contentType->identifier,
517 1
                            'parent_location' => $location->parentLocationId,
518 1
                            'priority' => $location->priority,
519 1
                            'is_hidden' => $location->invisible,
520 1
                            'sort_field' => $this->sortConverter->sortField2Hash($location->sortField),
521 1
                            'sort_order' => $this->sortConverter->sortOrder2Hash($location->sortOrder),
522 1
                            'remote_id' => $content->contentInfo->remoteId,
523 1
                            'location_remote_id' => $location->remoteId,
524 1
                            'section' => $content->contentInfo->sectionId,
525 1
                            'object_states' => $this->getObjectStates($content),
526
                        )
527
                    );
528 1
                    $locationService = $this->repository->getLocationService();
529
                    /// @todo for accurate replication, we should express the addinfg of 2ndary locatins as separate steps, and copy over visibility, priority etc
530 1
                    $locations = $locationService->loadLocations($content->contentInfo);
531 1
                    if (count($locations) > 1) {
532
                        $otherParentLocations = array();
533
                        foreach($locations as $otherLocation) {
534
                            if ($otherLocation->id != $location->id) {
535
                                $otherParentLocations[] = $otherLocation->parentLocationId;
536
                            }
537
                        }
538
                        $contentData['other_parent_locations'] = $otherParentLocations;
539
                    }
540 1
                    break;
541 2
                case 'update':
542 1
                    $contentData = array_merge(
543 1
                        $contentData,
544
                        array(
545
                            'match' => array(
546 1
                                ContentMatcher::MATCH_CONTENT_REMOTE_ID => $content->contentInfo->remoteId
547
                            ),
548 1
                            'new_remote_id' => $content->contentInfo->remoteId,
549 1
                            'section' => $content->contentInfo->sectionId,
550 1
                            'object_states' => $this->getObjectStates($content),
551
                        )
552
                    );
553 1
                    break;
554 1
                case 'delete':
555 1
                    $contentData = array_merge(
556 1
                        $contentData,
557
                        array(
558
                            'match' => array(
559 1
                                ContentMatcher::MATCH_CONTENT_REMOTE_ID => $content->contentInfo->remoteId
560
                            )
561
                        )
562
                    );
563 1
                    break;
564
                default:
565
                    throw new \Exception("Executor 'content' doesn't support mode '$mode'");
566
            }
567
568 3
            if ($mode != 'delete') {
569
570 2
                $attributes = array();
571 2
                foreach ($content->getFieldsByLanguage($this->getLanguageCodeFromContext($context)) as $fieldIdentifier => $field) {
572 2
                    $fieldDefinition = $contentType->getFieldDefinition($fieldIdentifier);
573 2
                    $attributes[$field->fieldDefIdentifier] = $this->fieldHandlerManager->fieldValueToHash(
574 2
                        $fieldDefinition->fieldTypeIdentifier, $contentType->identifier, $field->value
575
                    );
576
                }
577
578 2
                $contentData = array_merge(
579 2
                    $contentData,
580
                    array(
581 2
                        'lang' => $this->getLanguageCodeFromContext($context),
582 2
                        'section' => $content->contentInfo->sectionId,
583 2
                        'owner' => $content->contentInfo->ownerId,
584 2
                        'modification_date' => $content->contentInfo->modificationDate->getTimestamp(),
585 2
                        'publication_date' => $content->contentInfo->publishedDate->getTimestamp(),
586 2
                        'always_available' => (bool)$content->contentInfo->alwaysAvailable,
587 2
                        'attributes' => $attributes
588
                    )
589
                );
590
            }
591
592 3
            $data[] = $contentData;
593
        }
594
595 3
        $this->loginUser($previousUserId);
596 3
        return $data;
597
    }
598
599
    /**
600
     * Helper function to set the fields of a ContentCreateStruct based on the DSL attribute settings.
601
     *
602
     * @param ContentCreateStruct|ContentUpdateStruct $createOrUpdateStruct
603
     * @param array $fields see description of expected format in code below
604
     * @param ContentType $contentType
605
     * @param $step
606
     * @throws \Exception
607
     */
608 14
    protected function setFields($createOrUpdateStruct, array $fields, ContentType $contentType, $step)
609
    {
610 14
        $fields = $this->normalizeFieldDefs($fields, $step);
611
612 14
        foreach ($fields as $fieldIdentifier => $fieldLanguages) {
613 14
            foreach ($fieldLanguages as $language => $fieldValue) {
614 14
                if (!isset($contentType->fieldDefinitionsByIdentifier[$fieldIdentifier])) {
615 2
                    throw new \Exception("Field '$fieldIdentifier' is not present in content type '{$contentType->identifier}'");
616
                }
617
618 14
                $fieldDefinition = $contentType->fieldDefinitionsByIdentifier[$fieldIdentifier];
619 14
                $fieldValue = $this->getFieldValue($fieldValue, $fieldDefinition, $contentType->identifier, $step->context);
620 14
                $createOrUpdateStruct->setField($fieldIdentifier, $fieldValue, $language);
621
            }
622
        }
623 12
    }
624
625
    /**
626
     * Helper function to accommodate the definition of fields
627
     * - using a legacy DSL version
628
     * - using either single-language or multi-language style
629
     * 
630
     * @param array $fields
631
     * @return array
632
     */
633 14
    protected function normalizeFieldDefs($fields, $step)
634
    {
635 14
        $convertedFields = [];
636 14
        $i = 0;
637
        // the 'easy' yml: key = field name, value = value
638
        // deprecated: the 'legacy' yml: key = numerical index, value = array ( field name => value )
639 14
        foreach ($fields as $key => $field) {
640 14
            if ($key === $i && is_array($field) && count($field) == 1) {
641
                // each $field is one key value pair
642
                // eg.: $field = array($fieldIdentifier => $fieldValue)
643 9
                reset($field);
644 9
                $fieldIdentifier = key($field);
645 9
                $fieldValue = $field[$fieldIdentifier];
646
647 9
                $convertedFields[$fieldIdentifier] = $fieldValue;
648
            } else {
649 7
                $convertedFields[$key] = $field;
650
            }
651 14
            $i++;
652
        }
653
654
        // transform single-language field defs in multilang ones
655 14
        if (!$this->hasLanguageCodesAsKeys($convertedFields)) {
656 13
            $language = $this->getLanguageCode($step);
657
658 13
            foreach ($convertedFields as $fieldIdentifier => $fieldValue) {
659 13
                $convertedFields[$fieldIdentifier] = array($language => $fieldValue);
660
            }
661
        }
662
663 14
        return $convertedFields;
664
    }
665
666
    /**
667
     * Checks whether all fields are using multilang syntax ie. a valid language as key.
668
     *
669
     * @param array $fields
670
     * @return bool
671
     */
672 14
    protected function hasLanguageCodesAsKeys(array $fields)
673
    {
674 14
        $languageCodes = $this->getContentLanguageCodes();
675
676 14
        foreach ($fields as $fieldIdentifier => $fieldData) {
677 14
            if (!is_array($fieldData) || empty($fieldData)) {
678 12
                return false;
679
            }
680
681 5
            foreach ($fieldData as $key => $data) {
682 5
                if (!in_array($key, $languageCodes, true)) {
683 5
                    return false;
684
                }
685
            }
686
        }
687
688 2
        return true;
689
    }
690
691
    /**
692
     * Returns all enabled Languages in the repo.
693
     * @todo move to parent class?
694
     *
695
     * @return string[]
696
     */
697 14
    protected function getContentLanguageCodes()
698
    {
699 14
        return array_map(
700 14
            function($language) {
701 14
                return $language->languageCode;
702 14
            },
703 14
            array_filter(
704 14
                $this->repository->getContentLanguageService()->loadLanguages(),
705 14
                function ($language) {
706 14
                    return $language->enabled;
707 14
                }
708
            )
709
        );
710
    }
711
712 1
    protected function setSection(Content $content, $sectionKey)
713
    {
714 1
        $sectionKey = $this->referenceResolver->resolveReference($sectionKey);
715 1
        $section = $this->sectionMatcher->matchOneByKey($sectionKey);
716
717 1
        $sectionService = $this->repository->getSectionService();
718 1
        $sectionService->assignSection($content->contentInfo, $section);
719 1
    }
720
721 2
    protected function setObjectStates(Content $content, array $stateKeys)
722
    {
723 2
        foreach ($stateKeys as $stateKey) {
724 2
            $stateKey = $this->referenceResolver->resolveReference($stateKey);
725
            /** @var \eZ\Publish\API\Repository\Values\ObjectState\ObjectState $state */
726 2
            $state = $this->objectStateMatcher->matchOneByKey($stateKey);
727
728 2
            $stateService = $this->repository->getObjectStateService();
729 2
            $stateService->setContentState($content->contentInfo, $state->getObjectStateGroup(), $state);
730
        }
731 2
    }
732
733 1
    protected function setMainLocation(Content $content, $locationId)
734
    {
735 1
        $locationId = $this->referenceResolver->resolveReference($locationId);
736 1
        if (is_int($locationId) || ctype_digit($locationId)) {
737 1
            $location = $this->repository->getLocationService()->loadLocation($locationId);
738
        } else {
739
            $location = $this->repository->getLocationService()->loadLocationByRemoteId($locationId);
740
        }
741
742 1
        if ($location->contentInfo->id != $content->id) {
743
            throw new \Exception("Can not set main location {$location->id} to content {$content->id} as it belongs to another object");
744
        }
745
746 1
        $contentService = $this->repository->getContentService();
747 1
        $contentMetaDataUpdateStruct = $contentService->newContentMetadataUpdateStruct();
748 1
        $contentMetaDataUpdateStruct->mainLocationId = $location->id;
749 1
        $contentService->updateContentMetadata($location->contentInfo, $contentMetaDataUpdateStruct);
750 1
    }
751
752 2
    protected function getObjectStates(Content $content)
753
    {
754 2
        $states = [];
755
756 2
        $objectStateService = $this->repository->getObjectStateService();
757 2
        $groups = $objectStateService->loadObjectStateGroups();
758 2
        foreach ($groups as $group) {
759 2
            if (in_array($group->identifier, $this->ignoredStateGroupIdentifiers)) {
760 2
                continue;
761
            }
762
            $state = $objectStateService->getContentState($content->contentInfo, $group);
763
            $states[] = $group->identifier . '/' . $state->identifier;
764
        }
765
766 2
        return $states;
767
    }
768
769
    /**
770
     * Create the field value from the migration definition hash
771
     *
772
     * @param mixed $value
773
     * @param FieldDefinition $fieldDefinition
774
     * @param string $contentTypeIdentifier
775
     * @param array $context
776
     * @throws \InvalidArgumentException
777
     * @return mixed
778
     */
779 14
    protected function getFieldValue($value, FieldDefinition $fieldDefinition, $contentTypeIdentifier, array $context = array())
780
    {
781 14
        $fieldTypeIdentifier = $fieldDefinition->fieldTypeIdentifier;
782
783 14
        if (is_array($value) || $this->fieldHandlerManager->managesField($fieldTypeIdentifier, $contentTypeIdentifier)) {
784
            // since we now allow refs to be arrays, let's attempt a 1st pass at resolving them here instead of every single fieldHandler...
785 5
            if (is_string($value) && $this->fieldHandlerManager->doPreResolveStringReferences($fieldTypeIdentifier, $contentTypeIdentifier)) {
786 1
                $value = $this->referenceResolver->resolveReference($value);
787
            }
788
            // inject info about the current content type and field into the context
789 5
            $context['contentTypeIdentifier'] = $contentTypeIdentifier;
790 5
            $context['fieldIdentifier'] = $fieldDefinition->identifier;
791 5
            return $this->fieldHandlerManager->hashToFieldValue($fieldTypeIdentifier, $contentTypeIdentifier, $value, $context);
792
        }
793
794 12
        return $this->getSingleFieldValue($value, $fieldDefinition, $contentTypeIdentifier, $context);
795
    }
796
797
    /**
798
     * Create the field value for a primitive field from the migration definition hash
799
     *
800
     * @param mixed $value
801
     * @param FieldDefinition $fieldDefinition
802
     * @param string $contentTypeIdentifier
803
     * @param array $context
804
     * @throws \InvalidArgumentException
805
     * @return mixed
806
     */
807 12
    protected function getSingleFieldValue($value, FieldDefinition $fieldDefinition, $contentTypeIdentifier, array $context = array())
0 ignored issues
show
Unused Code introduced by
The parameter $context is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

807
    protected function getSingleFieldValue($value, FieldDefinition $fieldDefinition, $contentTypeIdentifier, /** @scrutinizer ignore-unused */ array $context = array())

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $fieldDefinition is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

807
    protected function getSingleFieldValue($value, /** @scrutinizer ignore-unused */ FieldDefinition $fieldDefinition, $contentTypeIdentifier, array $context = array())

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $contentTypeIdentifier is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

807
    protected function getSingleFieldValue($value, FieldDefinition $fieldDefinition, /** @scrutinizer ignore-unused */ $contentTypeIdentifier, array $context = array())

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
808
    {
809
        // booleans were handled here. They are now handled as complextypes
810
811
        // q: do we really want this to happen by default on all scalar field values?
812
        // Note: if you want this *not* to happen, register a complex field for your scalar field...
813 12
        $value = $this->referenceResolver->resolveReference($value);
814
815 12
        return $value;
816
    }
817
818
    /**
819
     * Load user using either login, email, id - resolving eventual references
820
     * @param int|string $userKey
821
     * @return \eZ\Publish\API\Repository\Values\User\User
822
     */
823 2
    protected function getUser($userKey)
824
    {
825 2
        $userKey = $this->referenceResolver->resolveReference($userKey);
826 2
        return $this->userMatcher->matchOneByKey($userKey);
827
    }
828
829
    /**
830
     * @param int|string $date if integer, we assume a timestamp
831
     * @return \DateTime
832
     */
833 1
    protected function toDateTime($date)
834
    {
835 1
        if (is_int($date)) {
836
            return new \DateTime("@" . $date);
837
        } else {
838 1
            return new \DateTime($date);
839
        }
840
    }
841
}
842