1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* File containing the Content Handler class. |
5
|
|
|
* |
6
|
|
|
* @copyright Copyright (C) eZ Systems AS. All rights reserved. |
7
|
|
|
* @license For full copyright and license information view LICENSE file distributed with this source code. |
8
|
|
|
*/ |
9
|
|
|
namespace eZ\Publish\Core\Persistence\Legacy\Content; |
10
|
|
|
|
11
|
|
|
use Exception; |
12
|
|
|
use eZ\Publish\Core\Persistence\Legacy\Content\Location\Gateway as LocationGateway; |
13
|
|
|
use eZ\Publish\SPI\Persistence\Content\Field; |
14
|
|
|
use eZ\Publish\SPI\Persistence\Content\Handler as BaseContentHandler; |
15
|
|
|
use eZ\Publish\SPI\Persistence\Content\Type\Handler as ContentTypeHandler; |
16
|
|
|
use eZ\Publish\Core\Persistence\Legacy\Content\UrlAlias\SlugConverter; |
17
|
|
|
use eZ\Publish\Core\Persistence\Legacy\Content\UrlAlias\Gateway as UrlAliasGateway; |
18
|
|
|
use eZ\Publish\SPI\Persistence\Content; |
19
|
|
|
use eZ\Publish\SPI\Persistence\Content\CreateStruct; |
20
|
|
|
use eZ\Publish\SPI\Persistence\Content\UpdateStruct; |
21
|
|
|
use eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct; |
22
|
|
|
use eZ\Publish\SPI\Persistence\Content\VersionInfo; |
23
|
|
|
use eZ\Publish\SPI\Persistence\Content\Relation\CreateStruct as RelationCreateStruct; |
24
|
|
|
use eZ\Publish\Core\Base\Exceptions\NotFoundException as NotFound; |
25
|
|
|
use Psr\Log\LoggerInterface; |
26
|
|
|
use Psr\Log\NullLogger; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* The Content Handler stores Content and ContentType objects. |
30
|
|
|
*/ |
31
|
|
|
class Handler implements BaseContentHandler |
32
|
|
|
{ |
33
|
|
|
/** |
34
|
|
|
* Content gateway. |
35
|
|
|
* |
36
|
|
|
* @var \eZ\Publish\Core\Persistence\Legacy\Content\Gateway |
37
|
|
|
*/ |
38
|
|
|
protected $contentGateway; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* Location gateway. |
42
|
|
|
* |
43
|
|
|
* @var \eZ\Publish\Core\Persistence\Legacy\Content\Location\Gateway |
44
|
|
|
*/ |
45
|
|
|
protected $locationGateway; |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* Mapper. |
49
|
|
|
* |
50
|
|
|
* @var Mapper |
51
|
|
|
*/ |
52
|
|
|
protected $mapper; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* FieldHandler. |
56
|
|
|
* |
57
|
|
|
* @var \eZ\Publish\Core\Persistence\Legacy\Content\FieldHandler |
58
|
|
|
*/ |
59
|
|
|
protected $fieldHandler; |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* URL slug converter. |
63
|
|
|
* |
64
|
|
|
* @var \eZ\Publish\Core\Persistence\Legacy\Content\UrlAlias\SlugConverter |
65
|
|
|
*/ |
66
|
|
|
protected $slugConverter; |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* UrlAlias gateway. |
70
|
|
|
* |
71
|
|
|
* @var \eZ\Publish\Core\Persistence\Legacy\Content\UrlAlias\Gateway |
72
|
|
|
*/ |
73
|
|
|
protected $urlAliasGateway; |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* ContentType handler. |
77
|
|
|
* |
78
|
|
|
* @var \eZ\Publish\SPI\Persistence\Content\Type\Handler |
79
|
|
|
*/ |
80
|
|
|
protected $contentTypeHandler; |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* Tree handler. |
84
|
|
|
* |
85
|
|
|
* @var \eZ\Publish\Core\Persistence\Legacy\Content\TreeHandler |
86
|
|
|
*/ |
87
|
|
|
protected $treeHandler; |
88
|
|
|
|
89
|
|
|
/** |
90
|
|
|
* @var \Psr\Log\LoggerInterface |
91
|
|
|
*/ |
92
|
|
|
private $logger; |
93
|
|
|
|
94
|
|
|
/** |
95
|
|
|
* Creates a new content handler. |
96
|
|
|
* |
97
|
|
|
* @param \eZ\Publish\Core\Persistence\Legacy\Content\Gateway $contentGateway |
98
|
|
|
* @param \eZ\Publish\Core\Persistence\Legacy\Content\Location\Gateway $locationGateway |
99
|
|
|
* @param \eZ\Publish\Core\Persistence\Legacy\Content\Mapper $mapper |
100
|
|
|
* @param \eZ\Publish\Core\Persistence\Legacy\Content\FieldHandler $fieldHandler |
101
|
|
|
* @param \eZ\Publish\Core\Persistence\Legacy\Content\UrlAlias\SlugConverter $slugConverter |
102
|
|
|
* @param \eZ\Publish\Core\Persistence\Legacy\Content\UrlAlias\Gateway $urlAliasGateway |
103
|
|
|
* @param \eZ\Publish\SPI\Persistence\Content\Type\Handler $contentTypeHandler |
104
|
|
|
* @param \eZ\Publish\Core\Persistence\Legacy\Content\TreeHandler $treeHandler |
105
|
|
|
* @param \Psr\Log\LoggerInterface|null $logger |
106
|
|
|
*/ |
107
|
|
|
public function __construct( |
108
|
|
|
Gateway $contentGateway, |
109
|
|
|
LocationGateway $locationGateway, |
110
|
|
|
Mapper $mapper, |
111
|
|
|
FieldHandler $fieldHandler, |
112
|
|
|
SlugConverter $slugConverter, |
113
|
|
|
UrlAliasGateway $urlAliasGateway, |
114
|
|
|
ContentTypeHandler $contentTypeHandler, |
115
|
|
|
TreeHandler $treeHandler, |
116
|
|
|
LoggerInterface $logger = null |
117
|
|
|
) { |
118
|
|
|
$this->contentGateway = $contentGateway; |
119
|
|
|
$this->locationGateway = $locationGateway; |
120
|
|
|
$this->mapper = $mapper; |
121
|
|
|
$this->fieldHandler = $fieldHandler; |
122
|
|
|
$this->slugConverter = $slugConverter; |
123
|
|
|
$this->urlAliasGateway = $urlAliasGateway; |
124
|
|
|
$this->contentTypeHandler = $contentTypeHandler; |
125
|
|
|
$this->treeHandler = $treeHandler; |
126
|
|
|
$this->logger = null !== $logger ? $logger : new NullLogger(); |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* Creates a new Content entity in the storage engine. |
131
|
|
|
* |
132
|
|
|
* The values contained inside the $content will form the basis of stored |
133
|
|
|
* entity. |
134
|
|
|
* |
135
|
|
|
* Will contain always a complete list of fields. |
136
|
|
|
* |
137
|
|
|
* @param \eZ\Publish\SPI\Persistence\Content\CreateStruct $struct Content creation struct. |
138
|
|
|
* |
139
|
|
|
* @return \eZ\Publish\SPI\Persistence\Content Content value object |
140
|
|
|
*/ |
141
|
|
|
public function create(CreateStruct $struct) |
142
|
|
|
{ |
143
|
|
|
return $this->internalCreate($struct); |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
/** |
147
|
|
|
* Creates a new Content entity in the storage engine. |
148
|
|
|
* |
149
|
|
|
* The values contained inside the $content will form the basis of stored |
150
|
|
|
* entity. |
151
|
|
|
* |
152
|
|
|
* Will contain always a complete list of fields. |
153
|
|
|
* |
154
|
|
|
* @param \eZ\Publish\SPI\Persistence\Content\CreateStruct $struct Content creation struct. |
155
|
|
|
* @param mixed $versionNo Used by self::copy() to maintain version numbers |
156
|
|
|
* |
157
|
|
|
* @return \eZ\Publish\SPI\Persistence\Content Content value object |
158
|
|
|
*/ |
159
|
|
|
protected function internalCreate(CreateStruct $struct, $versionNo = 1) |
160
|
|
|
{ |
161
|
|
|
$content = new Content(); |
162
|
|
|
|
163
|
|
|
$content->fields = $struct->fields; |
164
|
|
|
$content->versionInfo = $this->mapper->createVersionInfoFromCreateStruct($struct, $versionNo); |
165
|
|
|
|
166
|
|
|
$content->versionInfo->contentInfo->id = $this->contentGateway->insertContentObject($struct, $versionNo); |
167
|
|
|
$content->versionInfo->id = $this->contentGateway->insertVersion( |
168
|
|
|
$content->versionInfo, |
169
|
|
|
$struct->fields |
170
|
|
|
); |
171
|
|
|
|
172
|
|
|
$contentType = $this->contentTypeHandler->load($struct->typeId); |
173
|
|
|
$this->fieldHandler->createNewFields($content, $contentType); |
174
|
|
|
|
175
|
|
|
// Create node assignments |
176
|
|
|
foreach ($struct->locations as $location) { |
177
|
|
|
$location->contentId = $content->versionInfo->contentInfo->id; |
178
|
|
|
$location->contentVersion = $content->versionInfo->versionNo; |
179
|
|
|
$this->locationGateway->createNodeAssignment( |
180
|
|
|
$location, |
181
|
|
|
$location->parentId, |
182
|
|
|
LocationGateway::NODE_ASSIGNMENT_OP_CODE_CREATE |
183
|
|
|
); |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
// Create names |
187
|
|
View Code Duplication |
foreach ($content->versionInfo->names as $language => $name) { |
|
|
|
|
188
|
|
|
$this->contentGateway->setName( |
189
|
|
|
$content->versionInfo->contentInfo->id, |
190
|
|
|
$content->versionInfo->versionNo, |
191
|
|
|
$name, |
192
|
|
|
$language |
193
|
|
|
); |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
return $content; |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
/** |
200
|
|
|
* Performs the publishing operations required to set the version identified by $updateStruct->versionNo and |
201
|
|
|
* $updateStruct->id as the published one. |
202
|
|
|
* |
203
|
|
|
* The publish procedure will: |
204
|
|
|
* - Create location nodes based on the node assignments |
205
|
|
|
* - Update the content object using the provided metadata update struct |
206
|
|
|
* - Update the node assignments |
207
|
|
|
* - Update location nodes of the content with the new published version |
208
|
|
|
* - Set content and version status to published |
209
|
|
|
* |
210
|
|
|
* @param int $contentId |
211
|
|
|
* @param int $versionNo |
212
|
|
|
* @param \eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct $metaDataUpdateStruct |
213
|
|
|
* |
214
|
|
|
* @return \eZ\Publish\SPI\Persistence\Content The published Content |
215
|
|
|
*/ |
216
|
|
|
public function publish($contentId, $versionNo, MetadataUpdateStruct $metaDataUpdateStruct) |
217
|
|
|
{ |
218
|
|
|
// Archive currently published version |
219
|
|
|
$versionInfo = $this->loadVersionInfo($contentId, $versionNo); |
220
|
|
|
if ($versionInfo->contentInfo->currentVersionNo != $versionNo) { |
221
|
|
|
$this->setStatus( |
222
|
|
|
$contentId, |
223
|
|
|
VersionInfo::STATUS_ARCHIVED, |
224
|
|
|
$versionInfo->contentInfo->currentVersionNo |
225
|
|
|
); |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
// Set always available name for the content |
229
|
|
|
$metaDataUpdateStruct->name = $versionInfo->names[$versionInfo->contentInfo->mainLanguageCode]; |
230
|
|
|
|
231
|
|
|
$this->contentGateway->updateContent($contentId, $metaDataUpdateStruct, $versionInfo); |
232
|
|
|
$this->locationGateway->createLocationsFromNodeAssignments( |
233
|
|
|
$contentId, |
234
|
|
|
$versionNo |
235
|
|
|
); |
236
|
|
|
|
237
|
|
|
$this->locationGateway->updateLocationsContentVersionNo($contentId, $versionNo); |
238
|
|
|
$this->setStatus($contentId, VersionInfo::STATUS_PUBLISHED, $versionNo); |
239
|
|
|
|
240
|
|
|
return $this->load($contentId, $versionNo); |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
/** |
244
|
|
|
* Creates a new draft version from $contentId in $version. |
245
|
|
|
* |
246
|
|
|
* Copies all fields from $contentId in $srcVersion and creates a new |
247
|
|
|
* version of the referred Content from it. |
248
|
|
|
* |
249
|
|
|
* Note: When creating a new draft in the old admin interface there will |
250
|
|
|
* also be an entry in the `eznode_assignment` created for the draft. This |
251
|
|
|
* is ignored in this implementation. |
252
|
|
|
* |
253
|
|
|
* @param mixed $contentId |
254
|
|
|
* @param mixed $srcVersion |
255
|
|
|
* @param mixed $userId |
256
|
|
|
* |
257
|
|
|
* @return \eZ\Publish\SPI\Persistence\Content |
258
|
|
|
*/ |
259
|
|
|
public function createDraftFromVersion($contentId, $srcVersion, $userId) |
260
|
|
|
{ |
261
|
|
|
$content = $this->load($contentId, $srcVersion); |
262
|
|
|
|
263
|
|
|
// Create new version |
264
|
|
|
$content->versionInfo = $this->mapper->createVersionInfoForContent( |
265
|
|
|
$content, |
266
|
|
|
$this->contentGateway->getLastVersionNumber($contentId) + 1, |
267
|
|
|
$userId |
268
|
|
|
); |
269
|
|
|
$content->versionInfo->id = $this->contentGateway->insertVersion( |
270
|
|
|
$content->versionInfo, |
271
|
|
|
$content->fields |
272
|
|
|
); |
273
|
|
|
|
274
|
|
|
// Clone fields from previous version and append them to the new one |
275
|
|
|
$this->fieldHandler->createExistingFieldsInNewVersion($content); |
276
|
|
|
|
277
|
|
|
// Create relations for new version |
278
|
|
|
$relations = $this->contentGateway->loadRelations($contentId, $srcVersion); |
279
|
|
|
foreach ($relations as $relation) { |
280
|
|
|
$this->contentGateway->insertRelation( |
281
|
|
|
new RelationCreateStruct( |
282
|
|
|
array( |
283
|
|
|
'sourceContentId' => $contentId, |
284
|
|
|
'sourceContentVersionNo' => $content->versionInfo->versionNo, |
285
|
|
|
'sourceFieldDefinitionId' => $relation['ezcontentobject_link_contentclassattribute_id'], |
286
|
|
|
'destinationContentId' => $relation['ezcontentobject_link_to_contentobject_id'], |
287
|
|
|
'type' => (int)$relation['ezcontentobject_link_relation_type'], |
288
|
|
|
) |
289
|
|
|
) |
290
|
|
|
); |
291
|
|
|
} |
292
|
|
|
|
293
|
|
|
// Create content names for new version |
294
|
|
|
foreach ($content->versionInfo->names as $language => $name) { |
295
|
|
|
$this->contentGateway->setName( |
296
|
|
|
$contentId, |
297
|
|
|
$content->versionInfo->versionNo, |
298
|
|
|
$name, |
299
|
|
|
$language |
300
|
|
|
); |
301
|
|
|
} |
302
|
|
|
|
303
|
|
|
return $content; |
304
|
|
|
} |
305
|
|
|
|
306
|
|
|
/** |
307
|
|
|
* {@inheritdoc} |
308
|
|
|
*/ |
309
|
|
|
public function load($id, $version = null, array $translations = null) |
310
|
|
|
{ |
311
|
|
|
$rows = $this->contentGateway->load($id, $version, $translations); |
|
|
|
|
312
|
|
|
|
313
|
|
|
if (empty($rows)) { |
314
|
|
|
throw new NotFound('content', "contentId: $id, versionNo: $version"); |
315
|
|
|
} |
316
|
|
|
|
317
|
|
|
$contentObjects = $this->mapper->extractContentFromRows( |
318
|
|
|
$rows, |
319
|
|
|
$this->contentGateway->loadVersionedNameData([[ |
320
|
|
|
'id' => $id, |
321
|
|
|
'version' => $rows[0]['ezcontentobject_version_version'], |
322
|
|
|
]]) |
323
|
|
|
); |
324
|
|
|
$content = $contentObjects[0]; |
325
|
|
|
unset($rows, $contentObjects); |
326
|
|
|
|
327
|
|
|
$this->fieldHandler->loadExternalFieldData($content); |
328
|
|
|
|
329
|
|
|
return $content; |
330
|
|
|
} |
331
|
|
|
|
332
|
|
|
/** |
333
|
|
|
* {@inheritdoc} |
334
|
|
|
*/ |
335
|
|
|
public function loadContentList(array $contentIds, array $translations = null): array |
336
|
|
|
{ |
337
|
|
|
$rawList = $this->contentGateway->loadContentList($contentIds, $translations); |
|
|
|
|
338
|
|
|
if (empty($rawList)) { |
339
|
|
|
return []; |
340
|
|
|
} |
341
|
|
|
|
342
|
|
|
$idVersionPairs = []; |
343
|
|
|
foreach ($rawList as $row) { |
344
|
|
|
// As there is only one version per id, set id as key to avoid duplicates |
345
|
|
|
$idVersionPairs[$row['ezcontentobject_id']] = [ |
346
|
|
|
'id' => $row['ezcontentobject_id'], |
347
|
|
|
'version' => $row['ezcontentobject_version_version'], |
348
|
|
|
]; |
349
|
|
|
} |
350
|
|
|
|
351
|
|
|
// group name data per Content Id |
352
|
|
|
$nameData = $this->contentGateway->loadVersionedNameData(array_values($idVersionPairs)); |
353
|
|
|
$contentItemNameData = []; |
354
|
|
|
foreach ($nameData as $nameDataRow) { |
355
|
|
|
$contentId = $nameDataRow['ezcontentobject_name_contentobject_id']; |
356
|
|
|
$contentItemNameData[$contentId][] = $nameDataRow; |
357
|
|
|
} |
358
|
|
|
|
359
|
|
|
// group rows per Content Id be able to ignore Content items with erroneous data |
360
|
|
|
$contentItemsRows = []; |
361
|
|
|
foreach ($rawList as $row) { |
362
|
|
|
$contentId = $row['ezcontentobject_id']; |
363
|
|
|
$contentItemsRows[$contentId][] = $row; |
364
|
|
|
} |
365
|
|
|
unset($rawList, $idVersionPairs); |
366
|
|
|
|
367
|
|
|
// try to extract Content from each Content data |
368
|
|
|
$contentItems = []; |
369
|
|
|
foreach ($contentItemsRows as $contentId => $contentItemsRow) { |
370
|
|
|
try { |
371
|
|
|
$contentList = $this->mapper->extractContentFromRows( |
372
|
|
|
$contentItemsRow, |
373
|
|
|
$contentItemNameData[$contentId] |
374
|
|
|
); |
375
|
|
|
$contentItems[$contentId] = $contentList[0]; |
376
|
|
|
} catch (Exception $e) { |
377
|
|
|
$this->logger->warning( |
378
|
|
|
sprintf( |
379
|
|
|
'%s: Content %d not loaded: %s', |
380
|
|
|
__METHOD__, |
381
|
|
|
$contentId, |
382
|
|
|
$e->getMessage() |
383
|
|
|
) |
384
|
|
|
); |
385
|
|
|
} |
386
|
|
|
} |
387
|
|
|
|
388
|
|
|
// try to load External Storage data for each Content, ignore Content items for which it failed |
389
|
|
|
foreach ($contentItems as $contentId => $content) { |
390
|
|
|
try { |
391
|
|
|
$this->fieldHandler->loadExternalFieldData($content); |
392
|
|
|
} catch (Exception $e) { |
393
|
|
|
unset($contentItems[$contentId]); |
394
|
|
|
$this->logger->warning( |
395
|
|
|
sprintf( |
396
|
|
|
'%s: Content %d not loaded: %s', |
397
|
|
|
__METHOD__, |
398
|
|
|
$contentId, |
399
|
|
|
$e->getMessage() |
400
|
|
|
) |
401
|
|
|
); |
402
|
|
|
} |
403
|
|
|
} |
404
|
|
|
|
405
|
|
|
return $contentItems; |
406
|
|
|
} |
407
|
|
|
|
408
|
|
|
/** |
409
|
|
|
* Returns the metadata object for a content identified by $contentId. |
410
|
|
|
* |
411
|
|
|
* @param int|string $contentId |
412
|
|
|
* |
413
|
|
|
* @return \eZ\Publish\SPI\Persistence\Content\ContentInfo |
414
|
|
|
*/ |
415
|
|
|
public function loadContentInfo($contentId) |
416
|
|
|
{ |
417
|
|
|
return $this->treeHandler->loadContentInfo($contentId); |
418
|
|
|
} |
419
|
|
|
|
420
|
|
|
public function loadContentInfoList(array $contentIds) |
421
|
|
|
{ |
422
|
|
|
$list = $this->mapper->extractContentInfoFromRows( |
423
|
|
|
$this->contentGateway->loadContentInfoList($contentIds) |
424
|
|
|
); |
425
|
|
|
|
426
|
|
|
$listByContentId = []; |
427
|
|
|
foreach ($list as $item) { |
428
|
|
|
$listByContentId[$item->id] = $item; |
429
|
|
|
} |
430
|
|
|
|
431
|
|
|
return $listByContentId; |
432
|
|
|
} |
433
|
|
|
|
434
|
|
|
/** |
435
|
|
|
* Returns the metadata object for a content identified by $remoteId. |
436
|
|
|
* |
437
|
|
|
* @param mixed $remoteId |
438
|
|
|
* |
439
|
|
|
* @return \eZ\Publish\SPI\Persistence\Content\ContentInfo |
440
|
|
|
*/ |
441
|
|
|
public function loadContentInfoByRemoteId($remoteId) |
442
|
|
|
{ |
443
|
|
|
return $this->mapper->extractContentInfoFromRow( |
444
|
|
|
$this->contentGateway->loadContentInfoByRemoteId($remoteId) |
445
|
|
|
); |
446
|
|
|
} |
447
|
|
|
|
448
|
|
|
/** |
449
|
|
|
* Returns the version object for a content/version identified by $contentId and $versionNo. |
450
|
|
|
* |
451
|
|
|
* @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException If version is not found |
452
|
|
|
* |
453
|
|
|
* @param int|string $contentId |
454
|
|
|
* @param int $versionNo Version number to load |
455
|
|
|
* |
456
|
|
|
* @return \eZ\Publish\SPI\Persistence\Content\VersionInfo |
457
|
|
|
*/ |
458
|
|
View Code Duplication |
public function loadVersionInfo($contentId, $versionNo) |
459
|
|
|
{ |
460
|
|
|
$rows = $this->contentGateway->loadVersionInfo($contentId, $versionNo); |
461
|
|
|
if (empty($rows)) { |
462
|
|
|
throw new NotFound('content', $contentId); |
463
|
|
|
} |
464
|
|
|
|
465
|
|
|
$versionInfo = $this->mapper->extractVersionInfoListFromRows( |
466
|
|
|
$rows, |
467
|
|
|
$this->contentGateway->loadVersionedNameData(array(array('id' => $contentId, 'version' => $versionNo))) |
468
|
|
|
); |
469
|
|
|
|
470
|
|
|
return reset($versionInfo); |
|
|
|
|
471
|
|
|
} |
472
|
|
|
|
473
|
|
|
/** |
474
|
|
|
* Returns all versions with draft status created by the given $userId. |
475
|
|
|
* |
476
|
|
|
* @param int $userId |
477
|
|
|
* |
478
|
|
|
* @return \eZ\Publish\SPI\Persistence\Content\VersionInfo[] |
479
|
|
|
*/ |
480
|
|
View Code Duplication |
public function loadDraftsForUser($userId) |
481
|
|
|
{ |
482
|
|
|
$rows = $this->contentGateway->listVersionsForUser($userId, VersionInfo::STATUS_DRAFT); |
483
|
|
|
if (empty($rows)) { |
484
|
|
|
return array(); |
485
|
|
|
} |
486
|
|
|
|
487
|
|
|
$idVersionPairs = array_map( |
488
|
|
|
function ($row) { |
489
|
|
|
return array( |
490
|
|
|
'id' => $row['ezcontentobject_version_contentobject_id'], |
491
|
|
|
'version' => $row['ezcontentobject_version_version'], |
492
|
|
|
); |
493
|
|
|
}, |
494
|
|
|
$rows |
495
|
|
|
); |
496
|
|
|
$nameRows = $this->contentGateway->loadVersionedNameData($idVersionPairs); |
497
|
|
|
|
498
|
|
|
return $this->mapper->extractVersionInfoListFromRows($rows, $nameRows); |
499
|
|
|
} |
500
|
|
|
|
501
|
|
|
/** |
502
|
|
|
* Sets the status of object identified by $contentId and $version to $status. |
503
|
|
|
* |
504
|
|
|
* The $status can be one of VersionInfo::STATUS_DRAFT, VersionInfo::STATUS_PUBLISHED, VersionInfo::STATUS_ARCHIVED |
505
|
|
|
* When status is set to VersionInfo::STATUS_PUBLISHED content status is updated to ContentInfo::STATUS_PUBLISHED |
506
|
|
|
* |
507
|
|
|
* @param int $contentId |
508
|
|
|
* @param int $status |
509
|
|
|
* @param int $version |
510
|
|
|
* |
511
|
|
|
* @return bool |
512
|
|
|
*/ |
513
|
|
|
public function setStatus($contentId, $status, $version) |
514
|
|
|
{ |
515
|
|
|
return $this->contentGateway->setStatus($contentId, $version, $status); |
516
|
|
|
} |
517
|
|
|
|
518
|
|
|
/** |
519
|
|
|
* Updates a content object meta data, identified by $contentId. |
520
|
|
|
* |
521
|
|
|
* @param int $contentId |
522
|
|
|
* @param \eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct $content |
523
|
|
|
* |
524
|
|
|
* @return \eZ\Publish\SPI\Persistence\Content\ContentInfo |
525
|
|
|
*/ |
526
|
|
|
public function updateMetadata($contentId, MetadataUpdateStruct $content) |
527
|
|
|
{ |
528
|
|
|
$this->contentGateway->updateContent($contentId, $content); |
529
|
|
|
$this->updatePathIdentificationString($contentId, $content); |
530
|
|
|
|
531
|
|
|
return $this->loadContentInfo($contentId); |
532
|
|
|
} |
533
|
|
|
|
534
|
|
|
/** |
535
|
|
|
* Updates path identification string for locations of given $contentId if main language |
536
|
|
|
* is set in update struct. |
537
|
|
|
* |
538
|
|
|
* This is specific to the Legacy storage engine, as path identification string is deprecated. |
539
|
|
|
* |
540
|
|
|
* @param int $contentId |
541
|
|
|
* @param \eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct $content |
542
|
|
|
*/ |
543
|
|
|
protected function updatePathIdentificationString($contentId, MetadataUpdateStruct $content) |
544
|
|
|
{ |
545
|
|
|
if (isset($content->mainLanguageId)) { |
546
|
|
|
$contentLocationsRows = $this->locationGateway->loadLocationDataByContent($contentId); |
547
|
|
|
foreach ($contentLocationsRows as $row) { |
548
|
|
|
$locationName = ''; |
549
|
|
|
$urlAliasRows = $this->urlAliasGateway->loadLocationEntries( |
550
|
|
|
$row['node_id'], |
551
|
|
|
false, |
552
|
|
|
$content->mainLanguageId |
553
|
|
|
); |
554
|
|
|
if (!empty($urlAliasRows)) { |
555
|
|
|
$locationName = $urlAliasRows[0]['text']; |
556
|
|
|
} |
557
|
|
|
$this->locationGateway->updatePathIdentificationString( |
558
|
|
|
$row['node_id'], |
559
|
|
|
$row['parent_node_id'], |
560
|
|
|
$this->slugConverter->convert( |
561
|
|
|
$locationName, |
562
|
|
|
'node_' . $row['node_id'], |
563
|
|
|
'urlalias_compat' |
564
|
|
|
) |
565
|
|
|
); |
566
|
|
|
} |
567
|
|
|
} |
568
|
|
|
} |
569
|
|
|
|
570
|
|
|
/** |
571
|
|
|
* Updates a content version, identified by $contentId and $versionNo. |
572
|
|
|
* |
573
|
|
|
* @param int $contentId |
574
|
|
|
* @param int $versionNo |
575
|
|
|
* @param \eZ\Publish\SPI\Persistence\Content\UpdateStruct $updateStruct |
576
|
|
|
* |
577
|
|
|
* @return \eZ\Publish\SPI\Persistence\Content |
578
|
|
|
*/ |
579
|
|
|
public function updateContent($contentId, $versionNo, UpdateStruct $updateStruct) |
580
|
|
|
{ |
581
|
|
|
$content = $this->load($contentId, $versionNo); |
582
|
|
|
$this->contentGateway->updateVersion($contentId, $versionNo, $updateStruct); |
583
|
|
|
$contentType = $this->contentTypeHandler->load($content->versionInfo->contentInfo->contentTypeId); |
584
|
|
|
$this->fieldHandler->updateFields($content, $updateStruct, $contentType); |
585
|
|
|
foreach ($updateStruct->name as $language => $name) { |
586
|
|
|
$this->contentGateway->setName( |
587
|
|
|
$contentId, |
588
|
|
|
$versionNo, |
589
|
|
|
$name, |
590
|
|
|
$language |
591
|
|
|
); |
592
|
|
|
} |
593
|
|
|
|
594
|
|
|
return $this->load($contentId, $versionNo); |
595
|
|
|
} |
596
|
|
|
|
597
|
|
|
/** |
598
|
|
|
* Deletes all versions and fields, all locations (subtree), and all relations. |
599
|
|
|
* |
600
|
|
|
* Removes the relations, but not the related objects. All subtrees of the |
601
|
|
|
* assigned nodes of this content objects are removed (recursively). |
602
|
|
|
* |
603
|
|
|
* @param int $contentId |
604
|
|
|
* |
605
|
|
|
* @return bool |
606
|
|
|
*/ |
607
|
|
|
public function deleteContent($contentId) |
608
|
|
|
{ |
609
|
|
|
$contentLocations = $this->contentGateway->getAllLocationIds($contentId); |
610
|
|
|
if (empty($contentLocations)) { |
611
|
|
|
$this->removeRawContent($contentId); |
612
|
|
|
} else { |
613
|
|
|
foreach ($contentLocations as $locationId) { |
614
|
|
|
$this->treeHandler->removeSubtree($locationId); |
615
|
|
|
} |
616
|
|
|
} |
617
|
|
|
} |
618
|
|
|
|
619
|
|
|
/** |
620
|
|
|
* Deletes raw content data. |
621
|
|
|
* |
622
|
|
|
* @param int $contentId |
623
|
|
|
*/ |
624
|
|
|
public function removeRawContent($contentId) |
625
|
|
|
{ |
626
|
|
|
$this->treeHandler->removeRawContent($contentId); |
627
|
|
|
} |
628
|
|
|
|
629
|
|
|
/** |
630
|
|
|
* Deletes given version, its fields, node assignment, relations and names. |
631
|
|
|
* |
632
|
|
|
* Removes the relations, but not the related objects. |
633
|
|
|
* |
634
|
|
|
* @param int $contentId |
635
|
|
|
* @param int $versionNo |
636
|
|
|
* |
637
|
|
|
* @return bool |
638
|
|
|
*/ |
639
|
|
|
public function deleteVersion($contentId, $versionNo) |
640
|
|
|
{ |
641
|
|
|
$versionInfo = $this->loadVersionInfo($contentId, $versionNo); |
642
|
|
|
|
643
|
|
|
$this->locationGateway->deleteNodeAssignment($contentId, $versionNo); |
644
|
|
|
|
645
|
|
|
$this->fieldHandler->deleteFields($contentId, $versionInfo); |
646
|
|
|
|
647
|
|
|
$this->contentGateway->deleteRelations($contentId, $versionNo); |
648
|
|
|
$this->contentGateway->deleteVersions($contentId, $versionNo); |
649
|
|
|
$this->contentGateway->deleteNames($contentId, $versionNo); |
650
|
|
|
} |
651
|
|
|
|
652
|
|
|
/** |
653
|
|
|
* Returns the versions for $contentId. |
654
|
|
|
* |
655
|
|
|
* Result is returned with oldest version first (sorted by created, alternatively version id if auto increment). |
656
|
|
|
* |
657
|
|
|
* @param int $contentId |
658
|
|
|
* @param mixed|null $status Optional argument to filter versions by status, like {@see VersionInfo::STATUS_ARCHIVED}. |
659
|
|
|
* @param int $limit Limit for items returned, -1 means none. |
660
|
|
|
* |
661
|
|
|
* @return \eZ\Publish\SPI\Persistence\Content\VersionInfo[] |
662
|
|
|
*/ |
663
|
|
|
public function listVersions($contentId, $status = null, $limit = -1) |
664
|
|
|
{ |
665
|
|
|
return $this->treeHandler->listVersions($contentId, $status, $limit); |
666
|
|
|
} |
667
|
|
|
|
668
|
|
|
/** |
669
|
|
|
* Copy Content with Fields, Versions & Relations from $contentId in $version. |
670
|
|
|
* |
671
|
|
|
* {@inheritdoc} |
672
|
|
|
* |
673
|
|
|
* @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException If content or version is not found |
674
|
|
|
* |
675
|
|
|
* @param mixed $contentId |
676
|
|
|
* @param mixed|null $versionNo Copy all versions if left null |
677
|
|
|
* @param int|null $newOwnerId |
678
|
|
|
* |
679
|
|
|
* @return \eZ\Publish\SPI\Persistence\Content |
680
|
|
|
*/ |
681
|
|
|
public function copy($contentId, $versionNo = null, $newOwnerId = null) |
682
|
|
|
{ |
683
|
|
|
$currentVersionNo = isset($versionNo) ? |
684
|
|
|
$versionNo : |
685
|
|
|
$this->loadContentInfo($contentId)->currentVersionNo; |
686
|
|
|
|
687
|
|
|
// Copy content in given version or current version |
688
|
|
|
$createStruct = $this->mapper->createCreateStructFromContent( |
689
|
|
|
$this->load($contentId, $currentVersionNo) |
690
|
|
|
); |
691
|
|
|
if ($newOwnerId) { |
|
|
|
|
692
|
|
|
$createStruct->ownerId = $newOwnerId; |
693
|
|
|
} |
694
|
|
|
$content = $this->internalCreate($createStruct, $currentVersionNo); |
695
|
|
|
|
696
|
|
|
// If version was not passed also copy other versions |
697
|
|
|
if (!isset($versionNo)) { |
698
|
|
|
$contentType = $this->contentTypeHandler->load($createStruct->typeId); |
699
|
|
|
|
700
|
|
|
foreach ($this->listVersions($contentId) as $versionInfo) { |
701
|
|
|
if ($versionInfo->versionNo === $currentVersionNo) { |
702
|
|
|
continue; |
703
|
|
|
} |
704
|
|
|
|
705
|
|
|
$versionContent = $this->load($contentId, $versionInfo->versionNo); |
706
|
|
|
|
707
|
|
|
$versionContent->versionInfo->contentInfo->id = $content->versionInfo->contentInfo->id; |
708
|
|
|
$versionContent->versionInfo->modificationDate = $createStruct->modified; |
709
|
|
|
$versionContent->versionInfo->creationDate = $createStruct->modified; |
710
|
|
|
$versionContent->versionInfo->id = $this->contentGateway->insertVersion( |
711
|
|
|
$versionContent->versionInfo, |
712
|
|
|
$versionContent->fields |
713
|
|
|
); |
714
|
|
|
|
715
|
|
|
$this->fieldHandler->createNewFields($versionContent, $contentType); |
716
|
|
|
|
717
|
|
|
// Create names |
718
|
|
View Code Duplication |
foreach ($versionContent->versionInfo->names as $language => $name) { |
|
|
|
|
719
|
|
|
$this->contentGateway->setName( |
720
|
|
|
$content->versionInfo->contentInfo->id, |
721
|
|
|
$versionInfo->versionNo, |
722
|
|
|
$name, |
723
|
|
|
$language |
724
|
|
|
); |
725
|
|
|
} |
726
|
|
|
} |
727
|
|
|
|
728
|
|
|
// Batch copy relations for all versions |
729
|
|
|
$this->contentGateway->copyRelations($contentId, $content->versionInfo->contentInfo->id); |
730
|
|
|
} else { |
731
|
|
|
// Batch copy relations for published version |
732
|
|
|
$this->contentGateway->copyRelations($contentId, $content->versionInfo->contentInfo->id, $versionNo); |
733
|
|
|
} |
734
|
|
|
|
735
|
|
|
return $content; |
736
|
|
|
} |
737
|
|
|
|
738
|
|
|
/** |
739
|
|
|
* Creates a relation between $sourceContentId in $sourceContentVersionNo |
740
|
|
|
* and $destinationContentId with a specific $type. |
741
|
|
|
* |
742
|
|
|
* @todo Should the existence verifications happen here or is this supposed to be handled at a higher level? |
743
|
|
|
* |
744
|
|
|
* @param \eZ\Publish\SPI\Persistence\Content\Relation\CreateStruct $createStruct |
745
|
|
|
* |
746
|
|
|
* @return \eZ\Publish\SPI\Persistence\Content\Relation |
747
|
|
|
*/ |
748
|
|
|
public function addRelation(RelationCreateStruct $createStruct) |
749
|
|
|
{ |
750
|
|
|
$relation = $this->mapper->createRelationFromCreateStruct($createStruct); |
751
|
|
|
|
752
|
|
|
$relation->id = $this->contentGateway->insertRelation($createStruct); |
753
|
|
|
|
754
|
|
|
return $relation; |
755
|
|
|
} |
756
|
|
|
|
757
|
|
|
/** |
758
|
|
|
* Removes a relation by relation Id. |
759
|
|
|
* |
760
|
|
|
* @todo Should the existence verifications happen here or is this supposed to be handled at a higher level? |
761
|
|
|
* |
762
|
|
|
* @param mixed $relationId |
763
|
|
|
* @param int $type {@see \eZ\Publish\API\Repository\Values\Content\Relation::COMMON, |
764
|
|
|
* \eZ\Publish\API\Repository\Values\Content\Relation::EMBED, |
765
|
|
|
* \eZ\Publish\API\Repository\Values\Content\Relation::LINK, |
766
|
|
|
* \eZ\Publish\API\Repository\Values\Content\Relation::FIELD} |
767
|
|
|
*/ |
768
|
|
|
public function removeRelation($relationId, $type) |
769
|
|
|
{ |
770
|
|
|
$this->contentGateway->deleteRelation($relationId, $type); |
771
|
|
|
} |
772
|
|
|
|
773
|
|
|
/** |
774
|
|
|
* Loads relations from $sourceContentId. Optionally, loads only those with $type and $sourceContentVersionNo. |
775
|
|
|
* |
776
|
|
|
* @param mixed $sourceContentId Source Content ID |
777
|
|
|
* @param mixed|null $sourceContentVersionNo Source Content Version, null if not specified |
778
|
|
|
* @param int|null $type {@see \eZ\Publish\API\Repository\Values\Content\Relation::COMMON, |
779
|
|
|
* \eZ\Publish\API\Repository\Values\Content\Relation::EMBED, |
780
|
|
|
* \eZ\Publish\API\Repository\Values\Content\Relation::LINK, |
781
|
|
|
* \eZ\Publish\API\Repository\Values\Content\Relation::FIELD} |
782
|
|
|
* |
783
|
|
|
* @return \eZ\Publish\SPI\Persistence\Content\Relation[] |
784
|
|
|
*/ |
785
|
|
|
public function loadRelations($sourceContentId, $sourceContentVersionNo = null, $type = null) |
786
|
|
|
{ |
787
|
|
|
return $this->mapper->extractRelationsFromRows( |
788
|
|
|
$this->contentGateway->loadRelations($sourceContentId, $sourceContentVersionNo, $type) |
789
|
|
|
); |
790
|
|
|
} |
791
|
|
|
|
792
|
|
|
/** |
793
|
|
|
* Loads relations from $contentId. Optionally, loads only those with $type. |
794
|
|
|
* |
795
|
|
|
* Only loads relations against published versions. |
796
|
|
|
* |
797
|
|
|
* @param mixed $destinationContentId Destination Content ID |
798
|
|
|
* @param int|null $type {@see \eZ\Publish\API\Repository\Values\Content\Relation::COMMON, |
799
|
|
|
* \eZ\Publish\API\Repository\Values\Content\Relation::EMBED, |
800
|
|
|
* \eZ\Publish\API\Repository\Values\Content\Relation::LINK, |
801
|
|
|
* \eZ\Publish\API\Repository\Values\Content\Relation::FIELD} |
802
|
|
|
* |
803
|
|
|
* @return \eZ\Publish\SPI\Persistence\Content\Relation[] |
804
|
|
|
*/ |
805
|
|
|
public function loadReverseRelations($destinationContentId, $type = null) |
806
|
|
|
{ |
807
|
|
|
return $this->mapper->extractRelationsFromRows( |
808
|
|
|
$this->contentGateway->loadReverseRelations($destinationContentId, $type) |
809
|
|
|
); |
810
|
|
|
} |
811
|
|
|
|
812
|
|
|
/** |
813
|
|
|
* {@inheritdoc} |
814
|
|
|
*/ |
815
|
|
|
public function removeTranslationFromContent($contentId, $languageCode) |
816
|
|
|
{ |
817
|
|
|
@trigger_error( |
|
|
|
|
818
|
|
|
__METHOD__ . ' is deprecated, use deleteTranslationFromContent instead', |
819
|
|
|
E_USER_DEPRECATED |
820
|
|
|
); |
821
|
|
|
$this->deleteTranslationFromContent($contentId, $languageCode); |
822
|
|
|
} |
823
|
|
|
|
824
|
|
|
/** |
825
|
|
|
* {@inheritdoc} |
826
|
|
|
*/ |
827
|
|
|
public function deleteTranslationFromContent($contentId, $languageCode) |
828
|
|
|
{ |
829
|
|
|
$this->fieldHandler->deleteTranslationFromContentFields( |
830
|
|
|
$contentId, |
831
|
|
|
$this->listVersions($contentId), |
832
|
|
|
$languageCode |
833
|
|
|
); |
834
|
|
|
$this->contentGateway->deleteTranslationFromContent($contentId, $languageCode); |
835
|
|
|
} |
836
|
|
|
|
837
|
|
|
/** |
838
|
|
|
* {@inheritdoc} |
839
|
|
|
*/ |
840
|
|
|
public function deleteTranslationFromDraft($contentId, $versionNo, $languageCode) |
841
|
|
|
{ |
842
|
|
|
$versionInfo = $this->loadVersionInfo($contentId, $versionNo); |
843
|
|
|
|
844
|
|
|
$this->fieldHandler->deleteTranslationFromVersionFields( |
845
|
|
|
$versionInfo, |
846
|
|
|
$languageCode |
847
|
|
|
); |
848
|
|
|
$this->contentGateway->deleteTranslationFromVersion( |
849
|
|
|
$contentId, |
850
|
|
|
$versionNo, |
851
|
|
|
$languageCode |
852
|
|
|
); |
853
|
|
|
|
854
|
|
|
// get all [languageCode => name] entries except the removed Translation |
855
|
|
|
$names = array_filter( |
856
|
|
|
$versionInfo->names, |
857
|
|
|
function ($lang) use ($languageCode) { |
858
|
|
|
return $lang !== $languageCode; |
859
|
|
|
}, |
860
|
|
|
ARRAY_FILTER_USE_KEY |
861
|
|
|
); |
862
|
|
|
// set new Content name |
863
|
|
|
foreach ($names as $language => $name) { |
864
|
|
|
$this->contentGateway->setName( |
865
|
|
|
$contentId, |
866
|
|
|
$versionNo, |
867
|
|
|
$name, |
868
|
|
|
$language |
869
|
|
|
); |
870
|
|
|
} |
871
|
|
|
|
872
|
|
|
// reload entire Version w/o removed Translation |
873
|
|
|
return $this->load($contentId, $versionNo); |
874
|
|
|
} |
875
|
|
|
} |
876
|
|
|
|
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.