@@ -236,7 +236,7 @@ protected function updateDatabaseMysql()
236236 protected function uninstallRepeatableFieldsPlugin ()
237237 {
238238 $ app = Factory::getApplication ();
239- $ db = Factory::getDbo ();
239+ $ db = Factory::getDbo ();
240240
241241 // Check if the plg_fields_repeatable plugin is present
242242 $ extensionId = $ db ->setQuery (
@@ -285,10 +285,16 @@ protected function uninstallRepeatableFieldsPlugin()
285285 * holds the `fieldparams` of the `repeatable` type, $newFieldparams shall hold the `fieldparams`
286286 * of the `subfields` type.
287287 */
288- $ newFieldparams = array (
288+ $ newFieldparams = [
289289 'repeat ' => '1 ' ,
290- 'options ' => array (),
291- );
290+ 'options ' => [],
291+ ];
292+
293+ /**
294+ * This array is used to store the mapping between the name of form fields from Repeatable field
295+ * with ID of the child-fields. It will then be used to migrate data later
296+ */
297+ $ mapping = [];
292298
293299 // If this repeatable fields actually had child-fields (normally this is always the case)
294300 if (isset ($ oldFieldparams ->fields ) && is_object ($ oldFieldparams ->fields ))
@@ -311,24 +317,24 @@ protected function uninstallRepeatableFieldsPlugin()
311317 * of the `repeatable` instance. This is what we use $data for, we create a new custom field
312318 * for each of the sub fields of the `repeatable` instance.
313319 */
314- $ data = array (
315- 'context ' => $ row ->context ,
316- 'group_id ' => $ row ->group_id ,
317- 'title ' => $ oldField ->fieldname ,
318- 'name ' => (
320+ $ data = [
321+ 'context ' => $ row ->context ,
322+ 'group_id ' => $ row ->group_id ,
323+ 'title ' => $ oldField ->fieldname ,
324+ 'name ' => (
319325 $ fieldname_prefix
320326 . $ oldField ->fieldname
321327 . ($ fieldname_suffix > 0 ? ('_ ' . $ fieldname_suffix ) : '' )
322328 ),
323- 'label ' => $ oldField ->fieldname ,
324- 'default_value ' => $ row ->default_value ,
325- 'type ' => $ oldField ->fieldtype ,
326- 'description ' => $ row ->description ,
327- 'state ' => '1 ' ,
328- 'params ' => $ row ->params ,
329- 'language ' => '* ' ,
329+ 'label ' => $ oldField ->fieldname ,
330+ 'default_value ' => $ row ->default_value ,
331+ 'type ' => $ oldField ->fieldtype ,
332+ 'description ' => $ row ->description ,
333+ 'state ' => '1 ' ,
334+ 'params ' => $ row ->params ,
335+ 'language ' => '* ' ,
330336 'assigned_cat_ids ' => [-1 ],
331- ) ;
337+ ] ;
332338
333339 // `number` is not a valid custom field type, so use `text` instead.
334340 if ($ data ['type ' ] == 'number ' )
@@ -381,12 +387,14 @@ protected function uninstallRepeatableFieldsPlugin()
381387 }
382388
383389 // And tell our new `subfields` field about his child
384- $ newFieldparams ['options ' ][('option ' . $ newFieldCount )] = array (
390+ $ newFieldparams ['options ' ][('option ' . $ newFieldCount )] = [
385391 'customfield ' => $ subfield_id ,
386392 'render_values ' => '1 ' ,
387- ) ;
393+ ] ;
388394
389395 $ newFieldCount ++;
396+
397+ $ mapping [$ oldField ->fieldname ] = 'field ' . $ subfield_id ;
390398 }
391399 }
392400
@@ -398,6 +406,67 @@ protected function uninstallRepeatableFieldsPlugin()
398406 ->set ($ db ->quoteName ('fieldparams ' ) . ' = ' . $ db ->quote (json_encode ($ newFieldparams )))
399407 ->where ($ db ->quoteName ('id ' ) . ' = ' . $ db ->quote ($ row ->id ))
400408 )->execute ();
409+
410+ // Migrate data for this field
411+ $ query = $ db ->getQuery (true )
412+ ->select ('* ' )
413+ ->from ($ db ->quoteName ('#__fields_values ' ))
414+ ->where ($ db ->quoteName ('field_id ' ) . ' = ' . $ row ->id );
415+ $ db ->setQuery ($ query );
416+
417+ foreach ($ db ->loadObjectList () as $ rowFieldValue )
418+ {
419+ // Do not do the version if no data is entered for the custom field this item
420+ if (!$ rowFieldValue ->value )
421+ {
422+ continue ;
423+ }
424+
425+ /**
426+ * Here we will have to update the stored value of the field to new format
427+ * The key for each row changes from repeatable to row, for example repeatable0 to row0, and so on
428+ * The key for each sub-field change from name of field to field + ID of the new sub-field
429+ * Example data format stored in J3: {"repeatable0":{"id":"1","username":"admin"}}
430+ * Example data format stored in J4: {"row0":{"field1":"1","field2":"admin"}}
431+ */
432+ $ newFieldValue = [];
433+
434+ // Convert to array to change key
435+ $ fieldValue = json_decode ($ rowFieldValue ->value , true );
436+
437+ // If data could not be decoded for some reason, ignore
438+ if (!$ fieldValue )
439+ {
440+ continue ;
441+ }
442+
443+ foreach ($ fieldValue as $ rowKey => $ rowValue )
444+ {
445+ $ rowKey = str_replace ('repeatable ' , 'row ' , $ rowKey );
446+ $ newFieldValue [$ rowKey ] = [];
447+
448+ foreach ($ rowValue as $ subFieldName => $ subFieldValue )
449+ {
450+ if (isset ($ mapping [$ subFieldName ]))
451+ {
452+ $ newFieldValue [$ rowKey ][$ mapping [$ subFieldName ]] = $ subFieldValue ;
453+ }
454+ else
455+ {
456+ // Not found, use the old key to avoid data lost
457+ $ newFieldValue [$ subFieldName ] = $ subFieldValue ;
458+ }
459+ }
460+ }
461+
462+ $ query ->clear ()
463+ ->update ($ db ->quoteName ('#__fields_values ' ))
464+ ->set ($ db ->quoteName ('value ' ) . ' = ' . $ db ->quote (json_encode ($ newFieldValue )))
465+ ->where ($ db ->quoteName ('field_id ' ) . ' = ' . $ rowFieldValue ->field_id )
466+ ->where ($ db ->quoteName ('item_id ' ) . ' = ' . $ rowFieldValue ->item_id );
467+ $ db ->setQuery ($ query )
468+ ->execute ();
469+ }
401470 }
402471
403472 // Now, unprotect the plugin so we can uninstall it
0 commit comments