1616use eZ \Publish \SPI \Persistence \Content \Language \Handler as LanguageHandler ;
1717use eZ \Publish \SPI \Persistence \Content \Relation ;
1818use eZ \Publish \SPI \Persistence \Content \Relation \CreateStruct as RelationCreateStruct ;
19+ use eZ \Publish \SPI \Persistence \Content \Type \Handler as ContentTypeHandler ;
1920use eZ \Publish \SPI \Persistence \Content \VersionInfo ;
21+ use Ibexa \Contracts \Core \Event \Mapper \ResolveMissingFieldEvent ;
22+ use Symfony \Contracts \EventDispatcher \EventDispatcherInterface ;
2023
2124/**
2225 * Mapper for Content Handler.
2326 *
2427 * Performs mapping of Content objects.
28+ *
29+ * @phpstan-type TVersionedLanguageFieldDefinitionsMap array<
30+ * int, array<
31+ * int, array<
32+ * string, array<
33+ * int, \eZ\Publish\SPI\Persistence\Content\Type\FieldDefinition,
34+ * >
35+ * >
36+ * >
37+ * >
38+ * @phpstan-type TVersionedFieldMap array<
39+ * int, array<
40+ * int, array<
41+ * int, \eZ\Publish\SPI\Persistence\Content\Field,
42+ * >
43+ * >
44+ * >
45+ * @phpstan-type TVersionedNameMap array<
46+ * int, array<
47+ * int, array<
48+ * string, array<int, string>
49+ * >
50+ * >
51+ * >
52+ * @phpstan-type TContentInfoMap array<int, \eZ\Publish\SPI\Persistence\Content\ContentInfo>
53+ * @phpstan-type TVersionInfoMap array<
54+ * int, array<
55+ * int, \eZ\Publish\SPI\Persistence\Content\VersionInfo,
56+ * >
57+ * >
2558 */
2659class Mapper
2760{
@@ -40,15 +73,25 @@ class Mapper
4073 protected $ languageHandler ;
4174
4275 /**
43- * Creates a new mapper.
44- *
45- * @param \eZ\Publish\Core\Persistence\Legacy\Content\FieldValue\ConverterRegistry $converterRegistry
46- * @param \eZ\Publish\SPI\Persistence\Content\Language\Handler $languageHandler
76+ * @var \eZ\Publish\SPI\Persistence\Content\Type\Handler
4777 */
48- public function __construct (Registry $ converterRegistry , LanguageHandler $ languageHandler )
49- {
78+ private $ contentTypeHandler ;
79+
80+ /**
81+ * @var \Symfony\Contracts\EventDispatcher\EventDispatcherInterface
82+ */
83+ private $ eventDispatcher ;
84+
85+ public function __construct (
86+ Registry $ converterRegistry ,
87+ LanguageHandler $ languageHandler ,
88+ ContentTypeHandler $ contentTypeHandler ,
89+ EventDispatcherInterface $ eventDispatcher
90+ ) {
5091 $ this ->converterRegistry = $ converterRegistry ;
5192 $ this ->languageHandler = $ languageHandler ;
93+ $ this ->contentTypeHandler = $ contentTypeHandler ;
94+ $ this ->eventDispatcher = $ eventDispatcher ;
5295 }
5396
5497 /**
@@ -174,66 +217,166 @@ public function convertToStorageValue(Field $field)
174217 *
175218 * "$tableName_$columnName"
176219 *
177- * @param array $rows
178- * @param array $nameRows
220+ * @param array<array<string, scalar>> $rows
221+ * @param array<array<string, scalar>> $nameRows
222+ * @param string $prefix
179223 *
180224 * @return \eZ\Publish\SPI\Persistence\Content[]
225+ *
226+ * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
181227 */
182- public function extractContentFromRows (array $ rows , array $ nameRows , $ prefix = 'ezcontentobject_ ' )
183- {
228+ public function extractContentFromRows (
229+ array $ rows ,
230+ array $ nameRows ,
231+ string $ prefix = 'ezcontentobject_ '
232+ ): array {
184233 $ versionedNameData = [];
234+
185235 foreach ($ nameRows as $ row ) {
186- $ contentId = (int )$ row ['ezcontentobject_name_contentobject_id ' ];
187- $ versionNo = (int )$ row ['ezcontentobject_name_content_version ' ];
188- $ versionedNameData [$ contentId ][$ versionNo ][$ row ['ezcontentobject_name_content_translation ' ]] = $ row ['ezcontentobject_name_name ' ];
236+ $ contentId = (int )$ row ["{$ prefix }name_contentobject_id " ];
237+ $ versionNo = (int )$ row ["{$ prefix }name_content_version " ];
238+ $ languageCode = $ row ["{$ prefix }name_content_translation " ];
239+ $ versionedNameData [$ contentId ][$ versionNo ][$ languageCode ] = $ row ["{$ prefix }name_name " ];
189240 }
190241
191242 $ contentInfos = [];
192243 $ versionInfos = [];
193244 $ fields = [];
194245
246+ $ fieldDefinitions = $ this ->loadCachedVersionFieldDefinitionsPerLanguage (
247+ $ rows ,
248+ $ prefix
249+ );
250+
195251 foreach ($ rows as $ row ) {
196252 $ contentId = (int )$ row ["{$ prefix }id " ];
253+ $ versionId = (int )$ row ["{$ prefix }version_id " ];
254+
197255 if (!isset ($ contentInfos [$ contentId ])) {
198256 $ contentInfos [$ contentId ] = $ this ->extractContentInfoFromRow ($ row , $ prefix );
199257 }
258+
200259 if (!isset ($ versionInfos [$ contentId ])) {
201260 $ versionInfos [$ contentId ] = [];
202261 }
203262
204- $ versionId = (int )$ row ['ezcontentobject_version_id ' ];
205263 if (!isset ($ versionInfos [$ contentId ][$ versionId ])) {
206264 $ versionInfos [$ contentId ][$ versionId ] = $ this ->extractVersionInfoFromRow ($ row );
207265 }
208266
209- $ fieldId = (int )$ row ['ezcontentobject_attribute_id ' ];
210- if (!isset ($ fields [$ contentId ][$ versionId ][$ fieldId ])) {
267+ $ fieldId = (int )$ row ["{$ prefix }attribute_id " ];
268+ $ fieldDefinitionId = (int )$ row ["{$ prefix }attribute_contentclassattribute_id " ];
269+ $ languageCode = $ row ["{$ prefix }attribute_language_code " ];
270+
271+ if (!isset ($ fields [$ contentId ][$ versionId ][$ fieldId ])
272+ && isset ($ fieldDefinitions [$ contentId ][$ versionId ][$ languageCode ][$ fieldDefinitionId ])
273+ ) {
211274 $ fields [$ contentId ][$ versionId ][$ fieldId ] = $ this ->extractFieldFromRow ($ row );
275+ unset($ fieldDefinitions [$ contentId ][$ versionId ][$ languageCode ][$ fieldDefinitionId ]);
212276 }
213277 }
214278
279+ return $ this ->buildContentObjects (
280+ $ contentInfos ,
281+ $ versionInfos ,
282+ $ fields ,
283+ $ fieldDefinitions ,
284+ $ versionedNameData
285+ );
286+ }
287+
288+ /**
289+ * @phpstan-param TContentInfoMap $contentInfos
290+ * @phpstan-param TVersionInfoMap $versionInfos
291+ * @phpstan-param TVersionedFieldMap $fields
292+ * @phpstan-param TVersionedLanguageFieldDefinitionsMap $missingFieldDefinitions
293+ * @phpstan-param TVersionedNameMap $versionedNames
294+ *
295+ * @return \eZ\Publish\SPI\Persistence\Content[]
296+ */
297+ private function buildContentObjects (
298+ array $ contentInfos ,
299+ array $ versionInfos ,
300+ array $ fields ,
301+ array $ missingFieldDefinitions ,
302+ array $ versionedNames
303+ ): array {
215304 $ results = [];
305+
216306 foreach ($ contentInfos as $ contentId => $ contentInfo ) {
217307 foreach ($ versionInfos [$ contentId ] as $ versionId => $ versionInfo ) {
218308 // Fallback to just main language name if versioned name data is missing
219- if (isset ($ versionedNameData [$ contentId ][$ versionInfo ->versionNo ])) {
220- $ names = $ versionedNameData [$ contentId ][$ versionInfo ->versionNo ];
221- } else {
222- $ names = [$ contentInfo ->mainLanguageCode => $ contentInfo ->name ];
223- }
309+ $ names = $ versionedNames [$ contentId ][$ versionInfo ->versionNo ]
310+ ?? [$ contentInfo ->mainLanguageCode => $ contentInfo ->name ];
224311
225312 $ content = new Content ();
226313 $ content ->versionInfo = $ versionInfo ;
227314 $ content ->versionInfo ->names = $ names ;
228315 $ content ->versionInfo ->contentInfo = $ contentInfo ;
229316 $ content ->fields = array_values ($ fields [$ contentId ][$ versionId ]);
317+
318+ $ missingVersionFieldDefinitions = $ missingFieldDefinitions [$ contentId ][$ versionId ];
319+ foreach ($ missingVersionFieldDefinitions as $ languageCode => $ versionFieldDefinitions ) {
320+ foreach ($ versionFieldDefinitions as $ fieldDefinition ) {
321+ $ event = $ this ->eventDispatcher ->dispatch (
322+ new ResolveMissingFieldEvent (
323+ $ content ,
324+ $ fieldDefinition ,
325+ $ languageCode
326+ )
327+ );
328+
329+ $ field = $ event ->getField ();
330+ if ($ field !== null ) {
331+ $ content ->fields [] = $ field ;
332+ }
333+ }
334+ }
335+
230336 $ results [] = $ content ;
231337 }
232338 }
233339
234340 return $ results ;
235341 }
236342
343+ /**
344+ * @phpstan-return TVersionedLanguageFieldDefinitionsMap
345+ *
346+ * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
347+ */
348+ private function loadCachedVersionFieldDefinitionsPerLanguage (
349+ array $ rows ,
350+ string $ prefix
351+ ): array {
352+ $ fieldDefinitions = [];
353+ $ contentTypes = [];
354+ $ allLanguages = $ this ->loadAllLanguagesWithIdKey ();
355+
356+ foreach ($ rows as $ row ) {
357+ $ contentId = (int )$ row ["{$ prefix }id " ];
358+ $ versionId = (int )$ row ["{$ prefix }version_id " ];
359+ $ contentTypeId = (int )$ row ["{$ prefix }contentclass_id " ];
360+ $ languageMask = (int )$ row ["{$ prefix }version_language_mask " ];
361+
362+ if (isset ($ fieldDefinitions [$ contentId ][$ versionId ])) {
363+ continue ;
364+ }
365+
366+ $ languageCodes = $ this ->extractLanguageCodesFromMask ($ languageMask , $ allLanguages );
367+ $ contentTypes [$ contentTypeId ] = $ contentTypes [$ contentTypeId ] ?? $ this ->contentTypeHandler ->load ($ contentTypeId );
368+ $ contentType = $ contentTypes [$ contentTypeId ];
369+ foreach ($ contentType ->fieldDefinitions as $ fieldDefinition ) {
370+ foreach ($ languageCodes as $ languageCode ) {
371+ $ id = $ fieldDefinition ->id ;
372+ $ fieldDefinitions [$ contentId ][$ versionId ][$ languageCode ][$ id ] = $ fieldDefinition ;
373+ }
374+ }
375+ }
376+
377+ return $ fieldDefinitions ;
378+ }
379+
237380 /**
238381 * Extracts a ContentInfo object from $row.
239382 *
@@ -251,7 +394,6 @@ public function extractContentInfoFromRow(array $row, $prefix = '', $treePrefix
251394 $ contentInfo ->contentTypeId = (int )$ row ["{$ prefix }contentclass_id " ];
252395 $ contentInfo ->sectionId = (int )$ row ["{$ prefix }section_id " ];
253396 $ contentInfo ->currentVersionNo = (int )$ row ["{$ prefix }current_version " ];
254- $ contentInfo ->isPublished = ($ row ["{$ prefix }status " ] == ContentInfo::STATUS_PUBLISHED );
255397 $ contentInfo ->ownerId = (int )$ row ["{$ prefix }owner_id " ];
256398 $ contentInfo ->publicationDate = (int )$ row ["{$ prefix }published " ];
257399 $ contentInfo ->modificationDate = (int )$ row ["{$ prefix }modified " ];
0 commit comments