|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
/** |
|
4
|
|
|
* File containing the DomainMapper 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\Repository\Helper; |
|
10
|
|
|
|
|
11
|
|
|
use eZ\Publish\API\Repository\Values\Content\Search\SearchResult; |
|
12
|
|
|
use eZ\Publish\API\Repository\Values\Content\Content as APIContent; |
|
13
|
|
|
use eZ\Publish\SPI\Persistence\Content\Handler as ContentHandler; |
|
14
|
|
|
use eZ\Publish\SPI\Persistence\Content\Location\Handler as LocationHandler; |
|
15
|
|
|
use eZ\Publish\SPI\Persistence\Content\Language\Handler as LanguageHandler; |
|
16
|
|
|
use eZ\Publish\SPI\Persistence\Content\Type\Handler as TypeHandler; |
|
17
|
|
|
use eZ\Publish\Core\Repository\Values\Content\Content; |
|
18
|
|
|
use eZ\Publish\Core\Repository\Values\Content\ContentProxy; |
|
19
|
|
|
use eZ\Publish\API\Repository\Values\Content\VersionInfo as APIVersionInfo; |
|
20
|
|
|
use eZ\Publish\Core\Repository\Values\Content\VersionInfo; |
|
21
|
|
|
use eZ\Publish\API\Repository\Values\Content\ContentInfo; |
|
22
|
|
|
use eZ\Publish\API\Repository\Values\ContentType\ContentType; |
|
23
|
|
|
use eZ\Publish\API\Repository\Values\Content\Field; |
|
24
|
|
|
use eZ\Publish\Core\Repository\Values\Content\Relation; |
|
25
|
|
|
use eZ\Publish\API\Repository\Values\Content\Location as APILocation; |
|
26
|
|
|
use eZ\Publish\Core\Repository\Values\Content\Location; |
|
27
|
|
|
use eZ\Publish\SPI\Persistence\Content as SPIContent; |
|
28
|
|
|
use eZ\Publish\SPI\Persistence\Content\Location as SPILocation; |
|
29
|
|
|
use eZ\Publish\SPI\Persistence\Content\VersionInfo as SPIVersionInfo; |
|
30
|
|
|
use eZ\Publish\SPI\Persistence\Content\ContentInfo as SPIContentInfo; |
|
31
|
|
|
use eZ\Publish\SPI\Persistence\Content\Relation as SPIRelation; |
|
32
|
|
|
use eZ\Publish\SPI\Persistence\Content\Type as SPIType; |
|
33
|
|
|
use eZ\Publish\SPI\Persistence\Content\Location\CreateStruct as SPILocationCreateStruct; |
|
34
|
|
|
use eZ\Publish\API\Repository\Exceptions\NotFoundException; |
|
35
|
|
|
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException; |
|
36
|
|
|
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentValue; |
|
37
|
|
|
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentType; |
|
38
|
|
|
use DateTime; |
|
39
|
|
|
|
|
40
|
|
|
/** |
|
41
|
|
|
* DomainMapper is an internal service. |
|
42
|
|
|
* |
|
43
|
|
|
* @internal Meant for internal use by Repository. |
|
44
|
|
|
*/ |
|
45
|
|
|
class DomainMapper |
|
46
|
|
|
{ |
|
47
|
|
|
const MAX_LOCATION_PRIORITY = 2147483647; |
|
48
|
|
|
const MIN_LOCATION_PRIORITY = -2147483648; |
|
49
|
|
|
|
|
50
|
|
|
/** |
|
51
|
|
|
* @var \eZ\Publish\SPI\Persistence\Content\Handler |
|
52
|
|
|
*/ |
|
53
|
|
|
protected $contentHandler; |
|
54
|
|
|
|
|
55
|
|
|
/** |
|
56
|
|
|
* @var \eZ\Publish\SPI\Persistence\Content\Location\Handler |
|
57
|
|
|
*/ |
|
58
|
|
|
protected $locationHandler; |
|
59
|
|
|
|
|
60
|
|
|
/** |
|
61
|
|
|
* @var \eZ\Publish\SPI\Persistence\Content\Type\Handler |
|
62
|
|
|
*/ |
|
63
|
|
|
protected $contentTypeHandler; |
|
64
|
|
|
|
|
65
|
|
|
/** |
|
66
|
|
|
* @var \eZ\Publish\SPI\Persistence\Content\Language\Handler |
|
67
|
|
|
*/ |
|
68
|
|
|
protected $contentLanguageHandler; |
|
69
|
|
|
|
|
70
|
|
|
/** |
|
71
|
|
|
* @var FieldTypeRegistry |
|
72
|
|
|
*/ |
|
73
|
|
|
protected $fieldTypeRegistry; |
|
74
|
|
|
|
|
75
|
|
|
/** |
|
76
|
|
|
* Setups service with reference to repository. |
|
77
|
|
|
* |
|
78
|
|
|
* @param \eZ\Publish\SPI\Persistence\Content\Handler $contentHandler |
|
79
|
|
|
* @param \eZ\Publish\SPI\Persistence\Content\Location\Handler $locationHandler |
|
80
|
|
|
* @param \eZ\Publish\SPI\Persistence\Content\Type\Handler $contentTypeHandler |
|
81
|
|
|
* @param \eZ\Publish\SPI\Persistence\Content\Language\Handler $contentLanguageHandler |
|
82
|
|
|
* @param FieldTypeRegistry $fieldTypeRegistry |
|
83
|
|
|
*/ |
|
84
|
|
|
public function __construct( |
|
85
|
|
|
ContentHandler $contentHandler, |
|
86
|
|
|
LocationHandler $locationHandler, |
|
87
|
|
|
TypeHandler $contentTypeHandler, |
|
88
|
|
|
LanguageHandler $contentLanguageHandler, |
|
89
|
|
|
FieldTypeRegistry $fieldTypeRegistry |
|
90
|
|
|
) { |
|
91
|
|
|
$this->contentHandler = $contentHandler; |
|
92
|
|
|
$this->locationHandler = $locationHandler; |
|
93
|
|
|
$this->contentTypeHandler = $contentTypeHandler; |
|
94
|
|
|
$this->contentLanguageHandler = $contentLanguageHandler; |
|
95
|
|
|
$this->fieldTypeRegistry = $fieldTypeRegistry; |
|
96
|
|
|
} |
|
97
|
|
|
|
|
98
|
|
|
/** |
|
99
|
|
|
* Builds a Content domain object from value object returned from persistence. |
|
100
|
|
|
* |
|
101
|
|
|
* @param \eZ\Publish\SPI\Persistence\Content $spiContent |
|
102
|
|
|
* @param ContentType|SPIType $contentType |
|
103
|
|
|
* @param array|null $fieldLanguages Language codes to filter fields on |
|
104
|
|
|
* @param string|null $fieldAlwaysAvailableLanguage Language code fallback if a given field is not found in $fieldLanguages |
|
105
|
|
|
* |
|
106
|
|
|
* @return \eZ\Publish\Core\Repository\Values\Content\Content |
|
107
|
|
|
*/ |
|
108
|
|
|
public function buildContentDomainObject(SPIContent $spiContent, $contentType = null, array $fieldLanguages = null, string $fieldAlwaysAvailableLanguage = null) |
|
109
|
|
|
{ |
|
110
|
|
|
if ($contentType === null) { |
|
111
|
|
|
$contentType = $this->contentTypeHandler->load( |
|
112
|
|
|
$spiContent->versionInfo->contentInfo->contentTypeId |
|
113
|
|
|
); |
|
114
|
|
|
} |
|
115
|
|
|
|
|
116
|
|
|
$prioritizedFieldLanguageCode = null; |
|
117
|
|
|
$prioritizedLanguages = $fieldLanguages ?: []; |
|
118
|
|
|
if (!empty($prioritizedLanguages)) { |
|
119
|
|
|
$availableFieldLanguageMap = array_fill_keys($spiContent->versionInfo->languageCodes, true); |
|
120
|
|
|
foreach ($prioritizedLanguages as $prioritizedLanguage) { |
|
121
|
|
|
if (isset($availableFieldLanguageMap[$prioritizedLanguage])) { |
|
122
|
|
|
$prioritizedFieldLanguageCode = $prioritizedLanguage; |
|
123
|
|
|
break; |
|
124
|
|
|
} |
|
125
|
|
|
} |
|
126
|
|
|
} |
|
127
|
|
|
|
|
128
|
|
|
return new Content( |
|
129
|
|
|
array( |
|
130
|
|
|
'internalFields' => $this->buildDomainFields($spiContent->fields, $contentType, $fieldLanguages, $fieldAlwaysAvailableLanguage), |
|
131
|
|
|
'versionInfo' => $this->buildVersionInfoDomainObject($spiContent->versionInfo, $prioritizedLanguages), |
|
132
|
|
|
'prioritizedFieldLanguageCode' => $prioritizedFieldLanguageCode, |
|
133
|
|
|
) |
|
134
|
|
|
); |
|
135
|
|
|
} |
|
136
|
|
|
|
|
137
|
|
|
/** |
|
138
|
|
|
* Builds a Content proxy object (lazy loaded, loads as soon as used). |
|
139
|
|
|
*/ |
|
140
|
|
|
public function buildContentProxy(int $id, array $prioritizedLanguages = []): APIContent |
|
141
|
|
|
{ |
|
142
|
|
|
$generator = $this->generatorForContentList([$id], $prioritizedLanguages); |
|
143
|
|
|
|
|
144
|
|
|
return new ContentProxy($generator, $id); |
|
145
|
|
|
} |
|
146
|
|
|
|
|
147
|
|
|
/** |
|
148
|
|
|
* Builds a list of Content proxy objects (lazy loaded, loads all as soon as one of them loads). |
|
149
|
|
|
*/ |
|
150
|
|
|
public function buildContentProxyList(array $ids, array $prioritizedLanguages = []): array |
|
151
|
|
|
{ |
|
152
|
|
|
$list = []; |
|
153
|
|
|
$generator = $this->generatorForContentList($ids, $prioritizedLanguages); |
|
154
|
|
|
foreach ($ids as $id) { |
|
155
|
|
|
$list[$id] = new ContentProxy($generator, $id); |
|
156
|
|
|
} |
|
157
|
|
|
|
|
158
|
|
|
return $list; |
|
159
|
|
|
} |
|
160
|
|
|
|
|
161
|
|
|
private function generatorForContentList(array $ids, array $prioritizedLanguages = []): \Generator |
|
162
|
|
|
{ |
|
163
|
|
|
$list = $this->contentHandler->loadContentList($ids, $prioritizedLanguages); |
|
164
|
|
|
|
|
165
|
|
|
while (!empty($list)) { |
|
166
|
|
|
$id = yield; |
|
167
|
|
|
$info = $list[$id]->versionInfo->contentInfo; |
|
168
|
|
|
yield $this->buildContentDomainObject( |
|
169
|
|
|
$list[$id], |
|
170
|
|
|
null,//@todo bulk load content type, AND(~/OR~) add in-memory cache for it which will also benefit all cases |
|
171
|
|
|
$prioritizedLanguages, |
|
172
|
|
|
$info->alwaysAvailable ? $info->mainLanguageCode : null |
|
173
|
|
|
); |
|
174
|
|
|
|
|
175
|
|
|
unset($list[$id]); |
|
176
|
|
|
} |
|
177
|
|
|
} |
|
178
|
|
|
|
|
179
|
|
|
/** |
|
180
|
|
|
* Returns an array of domain fields created from given array of SPI fields. |
|
181
|
|
|
* |
|
182
|
|
|
* @throws InvalidArgumentType On invalid $contentType |
|
183
|
|
|
* |
|
184
|
|
|
* @param \eZ\Publish\SPI\Persistence\Content\Field[] $spiFields |
|
185
|
|
|
* @param ContentType|SPIType $contentType |
|
186
|
|
|
* @param array $languages A language priority, filters returned fields and is used as prioritized language code on |
|
187
|
|
|
* returned value object. If not given all languages are returned. |
|
188
|
|
|
* @param string|null $alwaysAvailableLanguage Language code fallback if a given field is not found in $languages |
|
189
|
|
|
* |
|
190
|
|
|
* @return array |
|
191
|
|
|
*/ |
|
192
|
|
|
public function buildDomainFields(array $spiFields, $contentType, array $languages = null, string $alwaysAvailableLanguage = null) |
|
193
|
|
|
{ |
|
194
|
|
|
if (!$contentType instanceof SPIType && !$contentType instanceof ContentType) { |
|
195
|
|
|
throw new InvalidArgumentType('$contentType', 'SPI ContentType | API ContentType'); |
|
196
|
|
|
} |
|
197
|
|
|
|
|
198
|
|
|
$fieldIdentifierMap = array(); |
|
199
|
|
|
foreach ($contentType->fieldDefinitions as $fieldDefinitions) { |
|
200
|
|
|
$fieldIdentifierMap[$fieldDefinitions->id] = $fieldDefinitions->identifier; |
|
201
|
|
|
} |
|
202
|
|
|
|
|
203
|
|
|
$fieldInFilterLanguagesMap = array(); |
|
204
|
|
|
if ($languages !== null && $alwaysAvailableLanguage !== null) { |
|
205
|
|
|
foreach ($spiFields as $spiField) { |
|
206
|
|
|
if (in_array($spiField->languageCode, $languages)) { |
|
207
|
|
|
$fieldInFilterLanguagesMap[$spiField->fieldDefinitionId] = true; |
|
208
|
|
|
} |
|
209
|
|
|
} |
|
210
|
|
|
} |
|
211
|
|
|
|
|
212
|
|
|
$fields = array(); |
|
213
|
|
|
foreach ($spiFields as $spiField) { |
|
214
|
|
|
// We ignore fields in content not part of the content type |
|
215
|
|
|
if (!isset($fieldIdentifierMap[$spiField->fieldDefinitionId])) { |
|
216
|
|
|
continue; |
|
217
|
|
|
} |
|
218
|
|
|
|
|
219
|
|
|
if ($languages !== null && !in_array($spiField->languageCode, $languages)) { |
|
220
|
|
|
// If filtering is enabled we ignore fields in other languages then $fieldLanguages, if: |
|
221
|
|
|
if ($alwaysAvailableLanguage === null) { |
|
222
|
|
|
// Ignore field if we don't have $alwaysAvailableLanguageCode fallback |
|
223
|
|
|
continue; |
|
224
|
|
|
} elseif (!empty($fieldInFilterLanguagesMap[$spiField->fieldDefinitionId])) { |
|
225
|
|
|
// Ignore field if it exists in one of the filtered languages |
|
226
|
|
|
continue; |
|
227
|
|
|
} elseif ($spiField->languageCode !== $alwaysAvailableLanguage) { |
|
228
|
|
|
// Also ignore if field is not in $alwaysAvailableLanguageCode |
|
229
|
|
|
continue; |
|
230
|
|
|
} |
|
231
|
|
|
} |
|
232
|
|
|
|
|
233
|
|
|
$fields[] = new Field( |
|
234
|
|
|
array( |
|
235
|
|
|
'id' => $spiField->id, |
|
236
|
|
|
'value' => $this->fieldTypeRegistry->getFieldType($spiField->type) |
|
237
|
|
|
->fromPersistenceValue($spiField->value), |
|
238
|
|
|
'languageCode' => $spiField->languageCode, |
|
239
|
|
|
'fieldDefIdentifier' => $fieldIdentifierMap[$spiField->fieldDefinitionId], |
|
240
|
|
|
'fieldTypeIdentifier' => $spiField->type, |
|
241
|
|
|
) |
|
242
|
|
|
); |
|
243
|
|
|
} |
|
244
|
|
|
|
|
245
|
|
|
return $fields; |
|
246
|
|
|
} |
|
247
|
|
|
|
|
248
|
|
|
/** |
|
249
|
|
|
* Builds a VersionInfo domain object from value object returned from persistence. |
|
250
|
|
|
* |
|
251
|
|
|
* @param \eZ\Publish\SPI\Persistence\Content\VersionInfo $spiVersionInfo |
|
252
|
|
|
* @param array $prioritizedLanguages |
|
253
|
|
|
* |
|
254
|
|
|
* @return \eZ\Publish\Core\Repository\Values\Content\VersionInfo |
|
255
|
|
|
*/ |
|
256
|
|
|
public function buildVersionInfoDomainObject(SPIVersionInfo $spiVersionInfo, array $prioritizedLanguages = []) |
|
257
|
|
|
{ |
|
258
|
|
|
// Map SPI statuses to API |
|
259
|
|
|
switch ($spiVersionInfo->status) { |
|
260
|
|
|
case SPIVersionInfo::STATUS_ARCHIVED: |
|
261
|
|
|
$status = APIVersionInfo::STATUS_ARCHIVED; |
|
262
|
|
|
break; |
|
263
|
|
|
|
|
264
|
|
|
case SPIVersionInfo::STATUS_PUBLISHED: |
|
265
|
|
|
$status = APIVersionInfo::STATUS_PUBLISHED; |
|
266
|
|
|
break; |
|
267
|
|
|
|
|
268
|
|
|
case SPIVersionInfo::STATUS_DRAFT: |
|
269
|
|
|
default: |
|
270
|
|
|
$status = APIVersionInfo::STATUS_DRAFT; |
|
271
|
|
|
} |
|
272
|
|
|
|
|
273
|
|
|
// Find prioritised language among names |
|
274
|
|
|
$prioritizedNameLanguageCode = null; |
|
275
|
|
|
foreach ($prioritizedLanguages as $prioritizedLanguage) { |
|
276
|
|
|
if (isset($spiVersionInfo->names[$prioritizedLanguage])) { |
|
277
|
|
|
$prioritizedNameLanguageCode = $prioritizedLanguage; |
|
278
|
|
|
break; |
|
279
|
|
|
} |
|
280
|
|
|
} |
|
281
|
|
|
|
|
282
|
|
|
return new VersionInfo( |
|
283
|
|
|
array( |
|
284
|
|
|
'id' => $spiVersionInfo->id, |
|
285
|
|
|
'versionNo' => $spiVersionInfo->versionNo, |
|
286
|
|
|
'modificationDate' => $this->getDateTime($spiVersionInfo->modificationDate), |
|
287
|
|
|
'creatorId' => $spiVersionInfo->creatorId, |
|
288
|
|
|
'creationDate' => $this->getDateTime($spiVersionInfo->creationDate), |
|
289
|
|
|
'status' => $status, |
|
290
|
|
|
'initialLanguageCode' => $spiVersionInfo->initialLanguageCode, |
|
291
|
|
|
'languageCodes' => $spiVersionInfo->languageCodes, |
|
292
|
|
|
'names' => $spiVersionInfo->names, |
|
293
|
|
|
'contentInfo' => $this->buildContentInfoDomainObject($spiVersionInfo->contentInfo), |
|
294
|
|
|
'prioritizedNameLanguageCode' => $prioritizedNameLanguageCode, |
|
295
|
|
|
) |
|
296
|
|
|
); |
|
297
|
|
|
} |
|
298
|
|
|
|
|
299
|
|
|
/** |
|
300
|
|
|
* Builds a ContentInfo domain object from value object returned from persistence. |
|
301
|
|
|
* |
|
302
|
|
|
* @param \eZ\Publish\SPI\Persistence\Content\ContentInfo $spiContentInfo |
|
303
|
|
|
* |
|
304
|
|
|
* @return \eZ\Publish\API\Repository\Values\Content\ContentInfo |
|
305
|
|
|
*/ |
|
306
|
|
|
public function buildContentInfoDomainObject(SPIContentInfo $spiContentInfo) |
|
307
|
|
|
{ |
|
308
|
|
|
// Map SPI statuses to API |
|
309
|
|
|
switch ($spiContentInfo->status) { |
|
310
|
|
|
case SPIContentInfo::STATUS_TRASHED: |
|
311
|
|
|
$status = ContentInfo::STATUS_TRASHED; |
|
312
|
|
|
break; |
|
313
|
|
|
|
|
314
|
|
|
case SPIContentInfo::STATUS_PUBLISHED: |
|
315
|
|
|
$status = ContentInfo::STATUS_PUBLISHED; |
|
316
|
|
|
break; |
|
317
|
|
|
|
|
318
|
|
|
case SPIContentInfo::STATUS_DRAFT: |
|
319
|
|
|
default: |
|
320
|
|
|
$status = ContentInfo::STATUS_DRAFT; |
|
321
|
|
|
} |
|
322
|
|
|
|
|
323
|
|
|
return new ContentInfo( |
|
324
|
|
|
array( |
|
325
|
|
|
'id' => $spiContentInfo->id, |
|
326
|
|
|
'contentTypeId' => $spiContentInfo->contentTypeId, |
|
327
|
|
|
'name' => $spiContentInfo->name, |
|
328
|
|
|
'sectionId' => $spiContentInfo->sectionId, |
|
329
|
|
|
'currentVersionNo' => $spiContentInfo->currentVersionNo, |
|
330
|
|
|
'published' => $spiContentInfo->isPublished, |
|
|
|
|
|
|
331
|
|
|
'ownerId' => $spiContentInfo->ownerId, |
|
332
|
|
|
'modificationDate' => $spiContentInfo->modificationDate == 0 ? |
|
333
|
|
|
null : |
|
334
|
|
|
$this->getDateTime($spiContentInfo->modificationDate), |
|
335
|
|
|
'publishedDate' => $spiContentInfo->publicationDate == 0 ? |
|
336
|
|
|
null : |
|
337
|
|
|
$this->getDateTime($spiContentInfo->publicationDate), |
|
338
|
|
|
'alwaysAvailable' => $spiContentInfo->alwaysAvailable, |
|
339
|
|
|
'remoteId' => $spiContentInfo->remoteId, |
|
340
|
|
|
'mainLanguageCode' => $spiContentInfo->mainLanguageCode, |
|
341
|
|
|
'mainLocationId' => $spiContentInfo->mainLocationId, |
|
342
|
|
|
'status' => $status, |
|
343
|
|
|
) |
|
344
|
|
|
); |
|
345
|
|
|
} |
|
346
|
|
|
|
|
347
|
|
|
/** |
|
348
|
|
|
* Builds API Relation object from provided SPI Relation object. |
|
349
|
|
|
* |
|
350
|
|
|
* @param \eZ\Publish\SPI\Persistence\Content\Relation $spiRelation |
|
351
|
|
|
* @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $sourceContentInfo |
|
352
|
|
|
* @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContentInfo |
|
353
|
|
|
* |
|
354
|
|
|
* @return \eZ\Publish\API\Repository\Values\Content\Relation |
|
355
|
|
|
*/ |
|
356
|
|
|
public function buildRelationDomainObject( |
|
357
|
|
|
SPIRelation $spiRelation, |
|
358
|
|
|
ContentInfo $sourceContentInfo, |
|
359
|
|
|
ContentInfo $destinationContentInfo |
|
360
|
|
|
) { |
|
361
|
|
|
$sourceFieldDefinitionIdentifier = null; |
|
362
|
|
|
if ($spiRelation->sourceFieldDefinitionId !== null) { |
|
363
|
|
|
$contentType = $this->contentTypeHandler->load($sourceContentInfo->contentTypeId); |
|
364
|
|
|
foreach ($contentType->fieldDefinitions as $fieldDefinition) { |
|
365
|
|
|
if ($fieldDefinition->id !== $spiRelation->sourceFieldDefinitionId) { |
|
366
|
|
|
continue; |
|
367
|
|
|
} |
|
368
|
|
|
|
|
369
|
|
|
$sourceFieldDefinitionIdentifier = $fieldDefinition->identifier; |
|
370
|
|
|
break; |
|
371
|
|
|
} |
|
372
|
|
|
} |
|
373
|
|
|
|
|
374
|
|
|
return new Relation( |
|
375
|
|
|
array( |
|
376
|
|
|
'id' => $spiRelation->id, |
|
377
|
|
|
'sourceFieldDefinitionIdentifier' => $sourceFieldDefinitionIdentifier, |
|
378
|
|
|
'type' => $spiRelation->type, |
|
379
|
|
|
'sourceContentInfo' => $sourceContentInfo, |
|
380
|
|
|
'destinationContentInfo' => $destinationContentInfo, |
|
381
|
|
|
) |
|
382
|
|
|
); |
|
383
|
|
|
} |
|
384
|
|
|
|
|
385
|
|
|
/** |
|
386
|
|
|
* Builds domain location object from provided persistence location. |
|
387
|
|
|
* |
|
388
|
|
|
* @param \eZ\Publish\SPI\Persistence\Content\Location $spiLocation |
|
389
|
|
|
* @param \eZ\Publish\SPI\Persistence\Content\ContentInfo|null $contentInfo |
|
390
|
|
|
* @param \eZ\Publish\API\Repository\Values\Content\Content|null $content |
|
391
|
|
|
* |
|
392
|
|
|
* @return \eZ\Publish\API\Repository\Values\Content\Location |
|
393
|
|
|
*/ |
|
394
|
|
|
public function buildLocationDomainObject(SPILocation $spiLocation, SPIContentInfo $contentInfo = null, APIContent $content = null) |
|
395
|
|
|
{ |
|
396
|
|
|
// TODO: this is hardcoded workaround for missing ContentInfo on root location |
|
397
|
|
|
if ($spiLocation->id == 1) { |
|
398
|
|
|
$legacyDateTime = $this->getDateTime(1030968000); // first known commit of eZ Publish 3.x |
|
399
|
|
|
$contentInfo = new ContentInfo( |
|
400
|
|
|
array( |
|
401
|
|
|
'id' => 0, |
|
402
|
|
|
'name' => 'Top Level Nodes', |
|
403
|
|
|
'sectionId' => 1, |
|
404
|
|
|
'mainLocationId' => 1, |
|
405
|
|
|
'contentTypeId' => 1, |
|
406
|
|
|
'currentVersionNo' => 1, |
|
407
|
|
|
'published' => 1, |
|
408
|
|
|
'ownerId' => 14, // admin user |
|
409
|
|
|
'modificationDate' => $legacyDateTime, |
|
410
|
|
|
'publishedDate' => $legacyDateTime, |
|
411
|
|
|
'alwaysAvailable' => 1, |
|
412
|
|
|
'remoteId' => null, |
|
413
|
|
|
'mainLanguageCode' => 'eng-GB', |
|
414
|
|
|
) |
|
415
|
|
|
); |
|
416
|
|
|
// content is left as null in this case atm |
|
417
|
|
|
} else { |
|
418
|
|
|
$contentInfo = $this->buildContentInfoDomainObject( |
|
419
|
|
|
$contentInfo ?: $this->contentHandler->loadContentInfo($spiLocation->contentId) |
|
420
|
|
|
); |
|
421
|
|
|
|
|
422
|
|
|
if ($content === null) { |
|
423
|
|
|
$content = $this->buildContentProxy($spiLocation->contentId); |
|
424
|
|
|
} |
|
425
|
|
|
} |
|
426
|
|
|
|
|
427
|
|
|
return new Location( |
|
428
|
|
|
array( |
|
429
|
|
|
'content' => $content, |
|
430
|
|
|
'contentInfo' => $contentInfo, |
|
431
|
|
|
'id' => $spiLocation->id, |
|
432
|
|
|
'priority' => $spiLocation->priority, |
|
433
|
|
|
'hidden' => $spiLocation->hidden, |
|
434
|
|
|
'invisible' => $spiLocation->invisible, |
|
435
|
|
|
'remoteId' => $spiLocation->remoteId, |
|
436
|
|
|
'parentLocationId' => $spiLocation->parentId, |
|
437
|
|
|
'pathString' => $spiLocation->pathString, |
|
438
|
|
|
'depth' => $spiLocation->depth, |
|
439
|
|
|
'sortField' => $spiLocation->sortField, |
|
440
|
|
|
'sortOrder' => $spiLocation->sortOrder, |
|
441
|
|
|
) |
|
442
|
|
|
); |
|
443
|
|
|
} |
|
444
|
|
|
|
|
445
|
|
|
/** |
|
446
|
|
|
* Build API Content domain objects in bulk and apply to ContentSearchResult. |
|
447
|
|
|
* |
|
448
|
|
|
* Loading of Content objects are done in one operation. |
|
449
|
|
|
* |
|
450
|
|
|
* @param \eZ\Publish\API\Repository\Values\Content\Search\SearchResult $result SPI search result with SPI ContentInfo items as hits |
|
451
|
|
|
* @param array $languageFilter |
|
452
|
|
|
* |
|
453
|
|
|
* @return \eZ\Publish\SPI\Persistence\Content\ContentInfo[] ContentInfo we did not find content for is returned. |
|
454
|
|
|
*/ |
|
455
|
|
|
public function buildContentDomainObjectsOnSearchResult(SearchResult $result, array $languageFilter) |
|
456
|
|
|
{ |
|
457
|
|
|
if (empty($result->searchHits)) { |
|
458
|
|
|
return []; |
|
459
|
|
|
} |
|
460
|
|
|
|
|
461
|
|
|
$contentIds = []; |
|
462
|
|
|
foreach ($result->searchHits as $hit) { |
|
463
|
|
|
$contentIds[] = $hit->valueObject->id; |
|
|
|
|
|
|
464
|
|
|
} |
|
465
|
|
|
|
|
466
|
|
|
$contentList = $this->contentHandler->loadContentList( |
|
467
|
|
|
$contentIds, |
|
468
|
|
|
!empty($languageFilter['languages']) ? $languageFilter['languages'] : [] |
|
469
|
|
|
); |
|
470
|
|
|
|
|
471
|
|
|
$missingContentList = []; |
|
472
|
|
|
foreach ($result->searchHits as $key => $hit) { |
|
473
|
|
|
if (isset($contentList[$hit->valueObject->id])) { |
|
|
|
|
|
|
474
|
|
|
$hit->valueObject = $this->buildContentDomainObject( |
|
475
|
|
|
$contentList[$hit->valueObject->id], |
|
|
|
|
|
|
476
|
|
|
null,//@todo bulk load content type, AND(~/OR~) add in-memory cache for it which will also benefit all cases |
|
477
|
|
|
!empty($languageFilter['languages']) ? $languageFilter['languages'] : null, |
|
478
|
|
|
empty($languageFilter['useAlwaysAvailable']) ? null : $hit->valueObject->mainLanguageCode |
|
|
|
|
|
|
479
|
|
|
); |
|
480
|
|
|
} else { |
|
481
|
|
|
$missingContentList[] = $hit->valueObject; |
|
482
|
|
|
unset($result->searchHits[$key]); |
|
483
|
|
|
--$result->totalCount; |
|
484
|
|
|
} |
|
485
|
|
|
} |
|
486
|
|
|
|
|
487
|
|
|
return $missingContentList; |
|
488
|
|
|
} |
|
489
|
|
|
|
|
490
|
|
|
/** |
|
491
|
|
|
* Build API Location and corresponding ContentInfo domain objects and apply to LocationSearchResult. |
|
492
|
|
|
* |
|
493
|
|
|
* Loading of ContentInfo objects are done in one operation. |
|
494
|
|
|
* |
|
495
|
|
|
* @param \eZ\Publish\API\Repository\Values\Content\Search\SearchResult $result SPI search result with SPI Location items as hits |
|
496
|
|
|
* |
|
497
|
|
|
* @return \eZ\Publish\SPI\Persistence\Content\Location[] Locations we did not find content info for is returned. |
|
498
|
|
|
*/ |
|
499
|
|
|
public function buildLocationDomainObjectsOnSearchResult(SearchResult $result, array $languageFilter) |
|
500
|
|
|
{ |
|
501
|
|
|
if (empty($result->searchHits)) { |
|
502
|
|
|
return []; |
|
503
|
|
|
} |
|
504
|
|
|
|
|
505
|
|
|
$contentIds = []; |
|
506
|
|
|
foreach ($result->searchHits as $hit) { |
|
507
|
|
|
$contentIds[] = $hit->valueObject->contentId; |
|
|
|
|
|
|
508
|
|
|
} |
|
509
|
|
|
|
|
510
|
|
|
$missingLocations = []; |
|
511
|
|
|
$contentInfoList = $this->contentHandler->loadContentInfoList($contentIds); |
|
512
|
|
|
$contentList = $this->buildContentProxyList( |
|
513
|
|
|
$contentIds, |
|
514
|
|
|
!empty($languageFilter['languages']) ? $languageFilter['languages'] : [] |
|
515
|
|
|
); |
|
516
|
|
|
foreach ($result->searchHits as $key => $hit) { |
|
517
|
|
|
if (isset($contentInfoList[$hit->valueObject->contentId])) { |
|
|
|
|
|
|
518
|
|
|
$hit->valueObject = $this->buildLocationDomainObject( |
|
519
|
|
|
$hit->valueObject, |
|
|
|
|
|
|
520
|
|
|
$contentInfoList[$hit->valueObject->contentId], |
|
|
|
|
|
|
521
|
|
|
$contentList[$hit->valueObject->contentId] |
|
|
|
|
|
|
522
|
|
|
); |
|
523
|
|
|
} else { |
|
524
|
|
|
$missingLocations[] = $hit->valueObject; |
|
525
|
|
|
unset($result->searchHits[$key]); |
|
526
|
|
|
--$result->totalCount; |
|
527
|
|
|
} |
|
528
|
|
|
} |
|
529
|
|
|
|
|
530
|
|
|
return $missingLocations; |
|
531
|
|
|
} |
|
532
|
|
|
|
|
533
|
|
|
/** |
|
534
|
|
|
* Creates an array of SPI location create structs from given array of API location create structs. |
|
535
|
|
|
* |
|
536
|
|
|
* @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException |
|
537
|
|
|
* |
|
538
|
|
|
* @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct $locationCreateStruct |
|
539
|
|
|
* @param \eZ\Publish\API\Repository\Values\Content\Location $parentLocation |
|
540
|
|
|
* @param mixed $mainLocation |
|
541
|
|
|
* @param mixed $contentId |
|
542
|
|
|
* @param mixed $contentVersionNo |
|
543
|
|
|
* |
|
544
|
|
|
* @return \eZ\Publish\SPI\Persistence\Content\Location\CreateStruct |
|
545
|
|
|
*/ |
|
546
|
|
|
public function buildSPILocationCreateStruct( |
|
547
|
|
|
$locationCreateStruct, |
|
548
|
|
|
APILocation $parentLocation, |
|
549
|
|
|
$mainLocation, |
|
550
|
|
|
$contentId, |
|
551
|
|
|
$contentVersionNo |
|
552
|
|
|
) { |
|
553
|
|
|
if (!$this->isValidLocationPriority($locationCreateStruct->priority)) { |
|
554
|
|
|
throw new InvalidArgumentValue('priority', $locationCreateStruct->priority, 'LocationCreateStruct'); |
|
555
|
|
|
} |
|
556
|
|
|
|
|
557
|
|
|
if (!is_bool($locationCreateStruct->hidden)) { |
|
558
|
|
|
throw new InvalidArgumentValue('hidden', $locationCreateStruct->hidden, 'LocationCreateStruct'); |
|
559
|
|
|
} |
|
560
|
|
|
|
|
561
|
|
|
if ($locationCreateStruct->remoteId !== null && (!is_string($locationCreateStruct->remoteId) || empty($locationCreateStruct->remoteId))) { |
|
562
|
|
|
throw new InvalidArgumentValue('remoteId', $locationCreateStruct->remoteId, 'LocationCreateStruct'); |
|
563
|
|
|
} |
|
564
|
|
|
|
|
565
|
|
|
if ($locationCreateStruct->sortField !== null && !$this->isValidLocationSortField($locationCreateStruct->sortField)) { |
|
566
|
|
|
throw new InvalidArgumentValue('sortField', $locationCreateStruct->sortField, 'LocationCreateStruct'); |
|
567
|
|
|
} |
|
568
|
|
|
|
|
569
|
|
|
if ($locationCreateStruct->sortOrder !== null && !$this->isValidLocationSortOrder($locationCreateStruct->sortOrder)) { |
|
570
|
|
|
throw new InvalidArgumentValue('sortOrder', $locationCreateStruct->sortOrder, 'LocationCreateStruct'); |
|
571
|
|
|
} |
|
572
|
|
|
|
|
573
|
|
|
$remoteId = $locationCreateStruct->remoteId; |
|
574
|
|
|
if (null === $remoteId) { |
|
575
|
|
|
$remoteId = $this->getUniqueHash($locationCreateStruct); |
|
576
|
|
|
} else { |
|
577
|
|
|
try { |
|
578
|
|
|
$this->locationHandler->loadByRemoteId($remoteId); |
|
579
|
|
|
throw new InvalidArgumentException( |
|
580
|
|
|
'$locationCreateStructs', |
|
581
|
|
|
"Another Location with remoteId '{$remoteId}' exists" |
|
582
|
|
|
); |
|
583
|
|
|
} catch (NotFoundException $e) { |
|
584
|
|
|
// Do nothing |
|
585
|
|
|
} |
|
586
|
|
|
} |
|
587
|
|
|
|
|
588
|
|
|
return new SPILocationCreateStruct( |
|
589
|
|
|
array( |
|
590
|
|
|
'priority' => $locationCreateStruct->priority, |
|
591
|
|
|
'hidden' => $locationCreateStruct->hidden, |
|
592
|
|
|
// If we declare the new Location as hidden, it is automatically invisible |
|
593
|
|
|
// Otherwise it picks up visibility from parent Location |
|
594
|
|
|
// Note: There is no need to check for hidden status of parent, as hidden Location |
|
595
|
|
|
// is always invisible as well |
|
596
|
|
|
'invisible' => ($locationCreateStruct->hidden === true || $parentLocation->invisible), |
|
597
|
|
|
'remoteId' => $remoteId, |
|
598
|
|
|
'contentId' => $contentId, |
|
599
|
|
|
'contentVersion' => $contentVersionNo, |
|
600
|
|
|
// pathIdentificationString will be set in storage |
|
601
|
|
|
'pathIdentificationString' => null, |
|
602
|
|
|
'mainLocationId' => $mainLocation, |
|
603
|
|
|
'sortField' => $locationCreateStruct->sortField !== null ? $locationCreateStruct->sortField : Location::SORT_FIELD_NAME, |
|
604
|
|
|
'sortOrder' => $locationCreateStruct->sortOrder !== null ? $locationCreateStruct->sortOrder : Location::SORT_ORDER_ASC, |
|
605
|
|
|
'parentId' => $locationCreateStruct->parentLocationId, |
|
606
|
|
|
) |
|
607
|
|
|
); |
|
608
|
|
|
} |
|
609
|
|
|
|
|
610
|
|
|
/** |
|
611
|
|
|
* Checks if given $sortField value is one of the defined sort field constants. |
|
612
|
|
|
* |
|
613
|
|
|
* @param mixed $sortField |
|
614
|
|
|
* |
|
615
|
|
|
* @return bool |
|
616
|
|
|
*/ |
|
617
|
|
|
public function isValidLocationSortField($sortField) |
|
618
|
|
|
{ |
|
619
|
|
|
switch ($sortField) { |
|
620
|
|
|
case APILocation::SORT_FIELD_PATH: |
|
621
|
|
|
case APILocation::SORT_FIELD_PUBLISHED: |
|
622
|
|
|
case APILocation::SORT_FIELD_MODIFIED: |
|
623
|
|
|
case APILocation::SORT_FIELD_SECTION: |
|
624
|
|
|
case APILocation::SORT_FIELD_DEPTH: |
|
625
|
|
|
case APILocation::SORT_FIELD_CLASS_IDENTIFIER: |
|
626
|
|
|
case APILocation::SORT_FIELD_CLASS_NAME: |
|
627
|
|
|
case APILocation::SORT_FIELD_PRIORITY: |
|
628
|
|
|
case APILocation::SORT_FIELD_NAME: |
|
629
|
|
|
case APILocation::SORT_FIELD_MODIFIED_SUBNODE: |
|
|
|
|
|
|
630
|
|
|
case APILocation::SORT_FIELD_NODE_ID: |
|
631
|
|
|
case APILocation::SORT_FIELD_CONTENTOBJECT_ID: |
|
632
|
|
|
return true; |
|
633
|
|
|
} |
|
634
|
|
|
|
|
635
|
|
|
return false; |
|
636
|
|
|
} |
|
637
|
|
|
|
|
638
|
|
|
/** |
|
639
|
|
|
* Checks if given $sortOrder value is one of the defined sort order constants. |
|
640
|
|
|
* |
|
641
|
|
|
* @param mixed $sortOrder |
|
642
|
|
|
* |
|
643
|
|
|
* @return bool |
|
644
|
|
|
*/ |
|
645
|
|
|
public function isValidLocationSortOrder($sortOrder) |
|
646
|
|
|
{ |
|
647
|
|
|
switch ($sortOrder) { |
|
648
|
|
|
case APILocation::SORT_ORDER_DESC: |
|
649
|
|
|
case APILocation::SORT_ORDER_ASC: |
|
650
|
|
|
return true; |
|
651
|
|
|
} |
|
652
|
|
|
|
|
653
|
|
|
return false; |
|
654
|
|
|
} |
|
655
|
|
|
|
|
656
|
|
|
/** |
|
657
|
|
|
* Checks if given $priority is valid. |
|
658
|
|
|
* |
|
659
|
|
|
* @param int $priority |
|
660
|
|
|
* |
|
661
|
|
|
* @return bool |
|
662
|
|
|
*/ |
|
663
|
|
|
public function isValidLocationPriority($priority) |
|
664
|
|
|
{ |
|
665
|
|
|
if ($priority === null) { |
|
666
|
|
|
return true; |
|
667
|
|
|
} |
|
668
|
|
|
|
|
669
|
|
|
return is_int($priority) && $priority >= self::MIN_LOCATION_PRIORITY && $priority <= self::MAX_LOCATION_PRIORITY; |
|
670
|
|
|
} |
|
671
|
|
|
|
|
672
|
|
|
/** |
|
673
|
|
|
* Validates given translated list $list, which should be an array of strings with language codes as keys. |
|
674
|
|
|
* |
|
675
|
|
|
* @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException |
|
676
|
|
|
* |
|
677
|
|
|
* @param mixed $list |
|
678
|
|
|
* @param string $argumentName |
|
679
|
|
|
*/ |
|
680
|
|
|
public function validateTranslatedList($list, $argumentName) |
|
681
|
|
|
{ |
|
682
|
|
|
if (!is_array($list)) { |
|
683
|
|
|
throw new InvalidArgumentType($argumentName, 'array', $list); |
|
684
|
|
|
} |
|
685
|
|
|
|
|
686
|
|
|
foreach ($list as $languageCode => $translation) { |
|
687
|
|
|
$this->contentLanguageHandler->loadByLanguageCode($languageCode); |
|
688
|
|
|
|
|
689
|
|
|
if (!is_string($translation)) { |
|
690
|
|
|
throw new InvalidArgumentType($argumentName . "['$languageCode']", 'string', $translation); |
|
691
|
|
|
} |
|
692
|
|
|
} |
|
693
|
|
|
} |
|
694
|
|
|
|
|
695
|
|
|
/** |
|
696
|
|
|
* Returns \DateTime object from given $timestamp in environment timezone. |
|
697
|
|
|
* |
|
698
|
|
|
* This method is needed because constructing \DateTime with $timestamp will |
|
699
|
|
|
* return the object in UTC timezone. |
|
700
|
|
|
* |
|
701
|
|
|
* @param int $timestamp |
|
702
|
|
|
* |
|
703
|
|
|
* @return \DateTime |
|
704
|
|
|
*/ |
|
705
|
|
|
public function getDateTime($timestamp) |
|
706
|
|
|
{ |
|
707
|
|
|
$dateTime = new DateTime(); |
|
708
|
|
|
$dateTime->setTimestamp($timestamp); |
|
709
|
|
|
|
|
710
|
|
|
return $dateTime; |
|
711
|
|
|
} |
|
712
|
|
|
|
|
713
|
|
|
/** |
|
714
|
|
|
* Creates unique hash string for given $object. |
|
715
|
|
|
* |
|
716
|
|
|
* Used for remoteId. |
|
717
|
|
|
* |
|
718
|
|
|
* @param object $object |
|
719
|
|
|
* |
|
720
|
|
|
* @return string |
|
721
|
|
|
*/ |
|
722
|
|
|
public function getUniqueHash($object) |
|
723
|
|
|
{ |
|
724
|
|
|
return md5(uniqid(get_class($object), true)); |
|
725
|
|
|
} |
|
726
|
|
|
} |
|
727
|
|
|
|
This property has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.