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\Content\ContentCreateStruct; |
7
|
|
|
use eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct; |
8
|
|
|
use eZ\Publish\Core\FieldType\Checkbox\Value as CheckboxValue; |
9
|
|
|
|
10
|
|
|
/** |
11
|
|
|
* Implements the actions for managing (create/update/delete) Content in the system through |
12
|
|
|
* migrations and abstracts away the eZ Publish Public API. |
13
|
|
|
* |
14
|
|
|
* @todo add support for updating of content metadata |
15
|
|
|
*/ |
16
|
|
|
class ContentManager extends RepositoryExecutor |
17
|
|
|
{ |
18
|
|
|
protected $supportedStepTypes = array('content'); |
19
|
|
|
|
20
|
|
|
protected $complexFieldManager; |
21
|
|
|
|
22
|
|
|
public function __construct($complexFieldManager) |
23
|
|
|
{ |
24
|
|
|
$this->complexFieldManager = $complexFieldManager; |
25
|
|
|
} |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* @param string $action |
29
|
|
|
* @return ContentCollection |
30
|
|
|
* @throws \Exception |
31
|
|
|
*/ |
32
|
|
View Code Duplication |
protected function matchContents($action) |
|
|
|
|
33
|
|
|
{ |
34
|
|
|
if (!isset($this->dsl['object_id']) && !isset($this->dsl['remote_id']) && !isset($this->dsl['match'])) { |
35
|
|
|
throw new \Exception("The ID or remote ID of an object or a Match Condition is required to $action a new location."); |
36
|
|
|
} |
37
|
|
|
|
38
|
|
|
// Backwards compat |
39
|
|
|
if (!isset($this->dsl['match'])) { |
40
|
|
|
if (isset($this->dsl['object_id'])) { |
41
|
|
|
$this->dsl['match'] = array('content_id' => $this->dsl['object_id']); |
42
|
|
|
} elseif (isset($this->dsl['remote_id'])) { |
43
|
|
|
$this->dsl['match'] = array('content_remote_id' => $this->dsl['remote_id']); |
44
|
|
|
} |
45
|
|
|
} |
46
|
|
|
|
47
|
|
|
$match = $this->dsl['match']; |
48
|
|
|
|
49
|
|
|
// convert the references passed in the match |
50
|
|
|
foreach ($match as $condition => $values) { |
51
|
|
|
if (is_array($values)) { |
52
|
|
|
foreach ($values as $position => $value) { |
53
|
|
|
if ($this->referenceResolver->isReference($value)) { |
54
|
|
|
$match[$condition][$position] = $this->referenceResolver->getReferenceValue($value); |
55
|
|
|
} |
56
|
|
|
} |
57
|
|
|
} else { |
58
|
|
|
if ($this->referenceResolver->isReference($values)) { |
59
|
|
|
$match[$condition] = $this->referenceResolver->getReferenceValue($values); |
60
|
|
|
} |
61
|
|
|
} |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
return $this->contentMatcher->matchContent($match); |
|
|
|
|
65
|
|
|
} |
66
|
|
|
|
67
|
|
|
/** |
68
|
|
|
* Handle the content create migration action type |
69
|
|
|
*/ |
70
|
|
|
protected function create() |
71
|
|
|
{ |
72
|
|
|
$this->loginUser(); |
73
|
|
|
|
74
|
|
|
$contentService = $this->repository->getContentService(); |
75
|
|
|
$locationService = $this->repository->getLocationService(); |
76
|
|
|
$contentTypeService = $this->repository->getContentTypeService(); |
77
|
|
|
|
78
|
|
|
$contentTypeIdentifier = $this->dsl['content_type']; |
79
|
|
|
if ($this->referenceResolver->isReference($contentTypeIdentifier)) { |
80
|
|
|
$contentTypeIdentifier = $this->referenceResolver->getReferenceValue($contentTypeIdentifier); |
81
|
|
|
} |
82
|
|
|
$contentType = $contentTypeService->loadContentTypeByIdentifier($contentTypeIdentifier); |
83
|
|
|
|
84
|
|
|
// FIXME: Defaulting in language code for now |
85
|
|
|
$contentCreateStruct = $contentService->newContentCreateStruct($contentType, self::DEFAULT_LANGUAGE_CODE); |
86
|
|
|
$this->setFields($contentCreateStruct, $this->dsl['attributes']); |
87
|
|
|
|
88
|
|
|
if (array_key_exists('remote_id', $this->dsl)) { |
89
|
|
|
$contentCreateStruct->remoteId = $this->dsl['remote_id']; |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
// instantiate a location create struct from the parent location |
93
|
|
|
$locationId = $this->dsl['main_location']; |
94
|
|
|
if ($this->referenceResolver->isReference($locationId)) { |
95
|
|
|
$locationId = $this->referenceResolver->getReferenceValue($locationId); |
96
|
|
|
} |
97
|
|
|
$locationCreateStruct = $locationService->newLocationCreateStruct($locationId); |
98
|
|
|
if (array_key_exists('remote_id', $this->dsl)) { |
99
|
|
|
$locationCreateStruct->remoteId = $this->dsl['remote_id'] . '_location'; |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
if (array_key_exists('priority', $this->dsl)) { |
103
|
|
|
$locationCreateStruct->priority = $this->dsl['priority']; |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
$locations = array($locationCreateStruct); |
107
|
|
|
|
108
|
|
|
if (array_key_exists('other_locations', $this->dsl)) { |
109
|
|
View Code Duplication |
foreach ($this->dsl['other_locations'] as $otherLocation) { |
|
|
|
|
110
|
|
|
$locationId = $otherLocation; |
111
|
|
|
if ($this->referenceResolver->isReference($locationId)) { |
112
|
|
|
$locationId = $this->referenceResolver->getReferenceValue($otherLocation); |
113
|
|
|
} |
114
|
|
|
$secondaryLocationCreateStruct = $locationService->newLocationCreateStruct($locationId); |
115
|
|
|
array_push($locations, $secondaryLocationCreateStruct); |
116
|
|
|
} |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
// create a draft using the content and location create struct and publish it |
120
|
|
|
$draft = $contentService->createContent($contentCreateStruct, $locations); |
121
|
|
|
$content = $contentService->publishVersion($draft->versionInfo); |
122
|
|
|
|
123
|
|
|
$this->setReferences($content); |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
/** |
127
|
|
|
* Handle the content update migration action type |
128
|
|
|
*/ |
129
|
|
|
protected function update() |
130
|
|
|
{ |
131
|
|
|
$this->loginUser(); |
132
|
|
|
|
133
|
|
|
$contentService = $this->repository->getContentService(); |
134
|
|
|
$contentTypeService = $this->repository->getContentTypeService(); |
135
|
|
|
|
136
|
|
|
/*if (isset($this->dsl['object_id'])) { |
|
|
|
|
137
|
|
|
$objectId = $this->dsl['object_id']; |
138
|
|
|
if ($this->referenceResolver->isReference($objectId)) { |
139
|
|
|
$objectId = $this->referenceResolver->getReferenceValue($objectId); |
140
|
|
|
} |
141
|
|
|
$contentToUpdate = $contentService->loadContent($objectId); |
142
|
|
|
$contentInfo = $contentToUpdate->contentInfo; |
143
|
|
|
} else { |
144
|
|
|
$remoteId = $this->dsl['remote_id']; |
145
|
|
|
if ($this->referenceResolver->isReference($remoteId)) { |
146
|
|
|
$remoteId = $this->referenceResolver->getReferenceValue($remoteId); |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
//try { |
150
|
|
|
$contentInfo = $contentService->loadContentInfoByRemoteId($remoteId); |
151
|
|
|
// disabled in v2: we disallow this. For matching location-remote-id, use the 'match' keyword |
152
|
|
|
//} catch (\eZ\Publish\API\Repository\Exceptions\NotFoundException $e) { |
153
|
|
|
// $location = $this->repository->getLocationService()->loadLocationByRemoteId($remoteId); |
154
|
|
|
// $contentInfo = $location->contentInfo; |
155
|
|
|
//} |
156
|
|
|
}*/ |
157
|
|
|
|
158
|
|
|
$contentCollection = $this->matchContents('update'); |
159
|
|
|
|
160
|
|
|
if (count($contentCollection) > 1 && array_key_exists('references', $this->dsl)) { |
161
|
|
|
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"); |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
$contentType = null; |
165
|
|
|
|
166
|
|
|
foreach ($contentCollection as $content) { |
167
|
|
|
$contentInfo = $content->contentInfo; |
168
|
|
|
|
169
|
|
|
if ($contentType == null) { |
170
|
|
|
$contentTypeService->loadContentType($contentInfo->contentTypeId); |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
$contentUpdateStruct = $contentService->newContentUpdateStruct(); |
174
|
|
|
|
175
|
|
|
if (array_key_exists('attributes', $this->dsl)) { |
176
|
|
|
$this->setFieldsToUpdate($contentUpdateStruct, $this->dsl['attributes'], $contentType); |
|
|
|
|
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
$draft = $contentService->createContentDraft($contentInfo); |
180
|
|
|
$contentService->updateContent($draft->versionInfo,$contentUpdateStruct); |
181
|
|
|
$content = $contentService->publishVersion($draft->versionInfo); |
182
|
|
|
|
183
|
|
|
if (array_key_exists('new_remote_id', $this->dsl)) { |
184
|
|
|
// Update object remote ID |
185
|
|
|
$contentMetaDataUpdateStruct = $contentService->newContentMetadataUpdateStruct(); |
186
|
|
|
$contentMetaDataUpdateStruct->remoteId = $this->dsl['new_remote_id']; |
187
|
|
|
$content = $contentService->updateContentMetadata($content->contentInfo, $contentMetaDataUpdateStruct); |
188
|
|
|
|
189
|
|
|
// Update main location remote ID |
190
|
|
|
// removed in v2: this is NOT generic! |
191
|
|
|
//$locationService = $this->repository->getLocationService(); |
|
|
|
|
192
|
|
|
//$locationUpdateStruct = $locationService->newLocationUpdateStruct(); |
|
|
|
|
193
|
|
|
//$locationUpdateStruct->remoteId = $this->dsl['new_remote_id'] . '_location'; |
|
|
|
|
194
|
|
|
//$location = $locationService->loadLocation($content->contentInfo->mainLocationId); |
|
|
|
|
195
|
|
|
//$locationService->updateLocation($location, $locationUpdateStruct); |
|
|
|
|
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
$this->setReferences($contentCollection); |
199
|
|
|
} |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
/** |
203
|
|
|
* Handle the content delete migration action type |
204
|
|
|
*/ |
205
|
|
|
protected function delete() |
206
|
|
|
{ |
207
|
|
|
$this->loginUser(); |
208
|
|
|
|
209
|
|
|
$contentService = $this->repository->getContentService(); |
210
|
|
|
|
211
|
|
|
$contentCollection = $this->matchContents('delete'); |
212
|
|
|
|
213
|
|
|
foreach ($contentCollection as $content) { |
214
|
|
|
$contentService->deleteContent($content->contentInfo); |
215
|
|
|
} |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
/** |
219
|
|
|
* Helper function to set the fields of a ContentCreateStruct based on the DSL attribute settings. |
220
|
|
|
* |
221
|
|
|
* @param ContentCreateStruct $createStruct |
222
|
|
|
* @param array $fields |
223
|
|
|
*/ |
224
|
|
View Code Duplication |
protected function setFields(ContentCreateStruct &$createStruct, array $fields) |
|
|
|
|
225
|
|
|
{ |
226
|
|
|
foreach ($fields as $field) { |
227
|
|
|
// each $field is one key value pair |
228
|
|
|
// eg.: $field = array($fieldIdentifier => $fieldValue) |
|
|
|
|
229
|
|
|
$fieldIdentifier = key($field); |
230
|
|
|
|
231
|
|
|
$fieldTypeIdentifier = $createStruct->contentType->fieldDefinitionsByIdentifier[$fieldIdentifier]->fieldTypeIdentifier; |
|
|
|
|
232
|
|
|
|
233
|
|
|
if (is_array($field[$fieldIdentifier])) { |
234
|
|
|
// Complex field needs special handling eg.: ezimage, ezbinaryfile |
235
|
|
|
$fieldValue = $this->handleComplexField($fieldTypeIdentifier, $field[$fieldIdentifier]); |
236
|
|
|
} else { |
237
|
|
|
// Primitive field eg.: ezstring, ezxml etc. |
238
|
|
|
$fieldValue = $this->handleSingleField($fieldTypeIdentifier, $fieldIdentifier, $field[$fieldIdentifier]); |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
$createStruct->setField($fieldIdentifier, $fieldValue, self::DEFAULT_LANGUAGE_CODE); |
242
|
|
|
} |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
/** |
246
|
|
|
* Helper function to set the fields of a ContentUpdateStruct based on the DSL attribute settings. |
247
|
|
|
* |
248
|
|
|
* @param ContentUpdateStruct $updateStruct |
249
|
|
|
* @param array $fields |
250
|
|
|
*/ |
251
|
|
View Code Duplication |
protected function setFieldsToUpdate(ContentUpdateStruct &$updateStruct, array $fields, ContentType $contentType) |
|
|
|
|
252
|
|
|
{ |
253
|
|
|
foreach ($fields as $field) { |
254
|
|
|
// each $field is one key value pair |
255
|
|
|
// eg.: $field = array($fieldIdentifier => $fieldValue) |
|
|
|
|
256
|
|
|
$fieldIdentifier = key($field); |
257
|
|
|
|
258
|
|
|
$fieldTypeIdentifier = $contentType->fieldDefinitionsByIdentifier[$fieldIdentifier]->fieldTypeIdentifier; |
|
|
|
|
259
|
|
|
|
260
|
|
|
if (is_array($field[$fieldIdentifier])) { |
261
|
|
|
// Complex field needs special handling eg.: ezimage, ezbinaryfile |
262
|
|
|
$fieldValue = $this->handleComplexField($fieldTypeIdentifier, $field[$fieldIdentifier]); |
263
|
|
|
} else { |
264
|
|
|
// Primitive field eg.: ezstring, ezxml etc. |
265
|
|
|
$fieldValue = $this->handleSingleField($fieldTypeIdentifier, $fieldIdentifier, $field[$fieldIdentifier]); |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
$updateStruct->setField($fieldIdentifier, $fieldValue, self::DEFAULT_LANGUAGE_CODE); |
269
|
|
|
} |
270
|
|
|
} |
271
|
|
|
|
272
|
|
|
/** |
273
|
|
|
* Create the field value for a primitive field |
274
|
|
|
* This function is needed to get past validation on Checkbox fieldtype (eZP bug) |
275
|
|
|
* |
276
|
|
|
* @param string $fieldTypeIdentifier |
277
|
|
|
* @param string $identifier |
278
|
|
|
* @param mixed $value |
279
|
|
|
* @throws \InvalidArgumentException |
280
|
|
|
* @return object |
281
|
|
|
*/ |
282
|
|
|
protected function handleSingleField($fieldTypeIdentifier, $identifier, $value) |
|
|
|
|
283
|
|
|
{ |
284
|
|
|
switch ($fieldTypeIdentifier) { |
285
|
|
|
case 'ezboolean': |
286
|
|
|
$fieldValue = new CheckboxValue(($value == 1) ? true : false); |
287
|
|
|
break; |
288
|
|
|
default: |
289
|
|
|
$fieldValue = $value; |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
if ($this->referenceResolver->isReference($value)) { |
293
|
|
|
$fieldValue = $this->referenceResolver->getReferenceValue($value); |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
return $fieldValue; |
297
|
|
|
} |
298
|
|
|
|
299
|
|
|
/** |
300
|
|
|
* Create the field value for a complex field eg.: ezimage, ezfile |
301
|
|
|
* |
302
|
|
|
* @param string $fieldTypeIdentifier |
303
|
|
|
* @param array $fieldValueArray |
304
|
|
|
* @param array $context |
305
|
|
|
* @return object |
306
|
|
|
*/ |
307
|
|
|
protected function handleComplexField($fieldTypeIdentifier, array $fieldValueArray, array $context = array()) |
308
|
|
|
{ |
309
|
|
|
return $this->complexFieldManager->getComplexFieldValue($fieldTypeIdentifier, $fieldValueArray, $context); |
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
/** |
313
|
|
|
* Sets references to certain content attributes. |
314
|
|
|
* The Content Manager currently supports setting references to object_id and location_id |
315
|
|
|
* |
316
|
|
|
* @param \eZ\Publish\API\Repository\Values\Content\Content $content |
317
|
|
|
* @throws \InvalidArgumentException When trying to set a reference to an unsupported attribute |
318
|
|
|
* @return boolean |
319
|
|
|
* |
320
|
|
|
* @todo add support for other attributes: contentTypeId, contentTypeIdentifier, section, etc... |
321
|
|
|
*/ |
322
|
|
View Code Duplication |
protected function setReferences($content) |
|
|
|
|
323
|
|
|
{ |
324
|
|
|
if (!array_key_exists('references', $this->dsl)) { |
325
|
|
|
return false; |
326
|
|
|
} |
327
|
|
|
|
328
|
|
|
foreach ($this->dsl['references'] as $reference) { |
329
|
|
|
|
330
|
|
|
switch ($reference['attribute']) { |
331
|
|
|
case 'object_id': |
332
|
|
|
case 'id': |
333
|
|
|
$value = $content->id; |
334
|
|
|
break; |
335
|
|
|
case 'location_id': |
336
|
|
|
$value = $content->contentInfo->mainLocationId; |
337
|
|
|
break; |
338
|
|
|
default: |
339
|
|
|
throw new \InvalidArgumentException('Content Manager does not support setting references for attribute ' . $reference['attribute']); |
340
|
|
|
} |
341
|
|
|
|
342
|
|
|
$this->referenceResolver->addReference($reference['identifier'], $value); |
|
|
|
|
343
|
|
|
} |
344
|
|
|
|
345
|
|
|
return true; |
346
|
|
|
} |
347
|
|
|
} |
348
|
|
|
|
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.