diff --git a/administrator/components/com_admin/script.php b/administrator/components/com_admin/script.php index 484995232cec4..747f0fe57d1a3 100644 --- a/administrator/components/com_admin/script.php +++ b/administrator/components/com_admin/script.php @@ -18,6 +18,7 @@ use Joomla\CMS\Log\Log; use Joomla\CMS\Table\Table; use Joomla\Database\ParameterType; +use Joomla\Component\Fields\Administrator\Model\FieldModel; use Joomla\Database\UTF8MB4SupportInterface; /** @@ -98,6 +99,7 @@ public function update($installer) $this->updateAssets($installer); $this->clearStatsCache(); $this->convertTablesToUtf8mb4(true); + $this->uninstallRepeatableFieldsPlugin(); $this->cleanJoomlaCache(); // VERY IMPORTANT! THIS METHOD SHOULD BE CALLED LAST, SINCE IT COULD @@ -223,6 +225,199 @@ protected function updateDatabaseMysql() } } + /** + * Uninstalls the plg_fields_repeatable plugin and transforms its custom field instances + * to instances of the plg_fields_subfields plugin. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + protected function uninstallRepeatableFieldsPlugin() + { + $app = Factory::getApplication(); + $db = Factory::getDbo(); + + // Check if the plg_fields_repeatable plugin is present + $extensionId = $db->setQuery( + $db->getQuery(true) + ->select('extension_id') + ->from('#__extensions') + ->where('name = ' . $db->quote('plg_fields_repeatable')) + )->loadResult(); + + // Skip uninstalling when it doesn't exist + if (!$extensionId) + { + return; + } + + try + { + $db->transactionStart(); + + // Get the FieldsModelField, we need it in a sec + $fieldModel = $app->bootComponent('com_fields')->getMVCFactory()->createModel('Field', 'Administrator', ['ignore_request' => true]); + /** @var FieldModel $fieldModel */ + + // Now get a list of all `repeatable` custom field instances + $db->setQuery( + $db->getQuery(true) + ->select('*') + ->from('#__fields') + ->where($db->quoteName('type') . ' = ' . $db->quote('repeatable')) + ); + + // Execute the query and iterate over the `repeatable` instances + foreach ($db->loadObjectList() as $row) + { + // Skip broken rows - just a security measure, should not happen + if (!isset($row->fieldparams) || !($oldFieldparams = json_decode($row->fieldparams)) || !is_object($oldFieldparams)) + { + continue; + } + + /** + * We basically want to transform this `repeatable` type into a `subfields` type. While $oldFieldparams + * holds the `fieldparams` of the `repeatable` type, $newFieldparams shall hold the `fieldparams` + * of the `subfields` type. + */ + $newFieldparams = array( + 'repeat' => '1', + 'options' => array(), + ); + + // If this repeatable fields actually had child-fields (normally this is always the case) + if (isset($oldFieldparams->fields) && is_object($oldFieldparams->fields)) + { + // Small counter for the child-fields (aka sub fields) + $newFieldCount = 0; + + // Iterate over the sub fields + foreach (get_object_vars($oldFieldparams->fields) as $oldField) + { + // Used for field name collision prevention + $fieldname_prefix = ''; + $fieldname_suffix = 0; + + // Try to save the new sub field in a loop because of field name collisions + while (true) + { + /** + * We basically want to create a completely new custom fields instance for every sub field + * of the `repeatable` instance. This is what we use $data for, we create a new custom field + * for each of the sub fields of the `repeatable` instance. + */ + $data = array( + 'context' => $row->context, + 'group_id' => $row->group_id, + 'title' => $oldField->fieldname, + 'name' => ( + $fieldname_prefix + . $oldField->fieldname + . ($fieldname_suffix > 0 ? ('_' . $fieldname_suffix) : '') + ), + 'label' => $oldField->fieldname, + 'default_value' => $row->default_value, + 'type' => $oldField->fieldtype, + 'description' => $row->description, + 'state' => '1', + 'params' => $row->params, + 'language' => '*', + 'assigned_cat_ids' => [-1], + ); + + // `number` is not a valid custom field type, so use `text` instead. + if ($data['type'] == 'number') + { + $data['type'] = 'text'; + } + + // Reset the state because else \Joomla\CMS\MVC\Model\AdminModel will take an already + // existing value (e.g. from previous save) and do an UPDATE instead of INSERT. + $fieldModel->setState('field.id', 0); + + // If an error occurred when trying to save this. + if (!$fieldModel->save($data)) + { + // If the error is, that the name collided, increase the collision prevention + $error = $fieldModel->getError(); + + if ($error == 'COM_FIELDS_ERROR_UNIQUE_NAME') + { + // If this is the first time this error occurs, set only the prefix + if ($fieldname_prefix == '') + { + $fieldname_prefix = ($row->name . '_'); + } + else + { + // Else increase the suffix + $fieldname_suffix++; + } + + // And start again with the while loop. + continue 1; + } + + // Else bail out with the error. Something is totally wrong. + throw new \Exception($error); + } + + // Break out of the while loop, saving was successful. + break 1; + } + + // Get the newly created id + $subfield_id = $fieldModel->getState('field.id'); + + // Really check that it is valid + if (!is_numeric($subfield_id) || $subfield_id < 1) + { + throw new \Exception('Something went wrong.'); + } + + // And tell our new `subfields` field about his child + $newFieldparams['options'][('option' . $newFieldCount)] = array( + 'customfield' => $subfield_id, + 'render_values' => '1', + ); + + $newFieldCount++; + } + } + + // Write back the changed stuff to the database + $db->setQuery( + $db->getQuery(true) + ->update('#__fields') + ->set($db->quoteName('type') . ' = ' . $db->quote('subfields')) + ->set($db->quoteName('fieldparams') . ' = ' . $db->quote(json_encode($newFieldparams))) + ->where($db->quoteName('id') . ' = ' . $db->quote($row->id)) + )->execute(); + } + + // Now, unprotect the plugin so we can uninstall it + $db->setQuery( + $db->getQuery(true) + ->update('#__extensions') + ->set('protected = 0') + ->where($db->quoteName('extension_id') . ' = ' . $extensionId) + )->execute(); + + // And now uninstall the plugin + $installer = new Installer; + $installer->uninstall('plugin', $extensionId); + + $db->transactionCommit(); + } + catch (\Exception $e) + { + $db->transactionRollback(); + throw $e; + } + } + /** * Update the manifest caches * @@ -4518,6 +4713,10 @@ public function deleteUnexistingFiles() '/plugins/content/confirmconsent/fields/consentbox.php', '/plugins/editors/codemirror/layouts/editors/codemirror/init.php', '/plugins/editors/tinymce/field/skins.php', + '/plugins/fields/repeatable/params/repeatable.xml', + '/plugins/fields/repeatable/repeatable.php', + '/plugins/fields/repeatable/repeatable.xml', + '/plugins/fields/repeatable/tmpl/repeatable.php', '/plugins/quickicon/joomlaupdate/joomlaupdate.php', '/plugins/system/languagecode/language/en-GB/en-GB.plg_system_languagecode.ini', '/plugins/system/languagecode/language/en-GB/en-GB.plg_system_languagecode.sys.ini', @@ -5896,6 +6095,9 @@ public function deleteUnexistingFiles() '/administrator/components/com_actionlogs/libraries', '/administrator/components/com_actionlogs/helpers', '/administrator/components/com_actionlogs/controllers', + '/plugins/fields/repeatable/params', + '/plugins/fields/repeatable/tmpl', + '/plugins/fields/repeatable', ); foreach ($files as $file) diff --git a/administrator/language/en-GB/plg_fields_repeatable.ini b/administrator/language/en-GB/plg_fields_repeatable.ini deleted file mode 100644 index 11bd71fb98e4f..0000000000000 --- a/administrator/language/en-GB/plg_fields_repeatable.ini +++ /dev/null @@ -1,16 +0,0 @@ -; Joomla! Project -; Copyright (C) 2005 - 2019 Open Source Matters. All rights reserved. -; License GNU General Public License version 2 or later; see LICENSE.txt -; Note : All ini files need to be saved as UTF-8 - -PLG_FIELDS_REPEATABLE="Fields - Repeatable" -PLG_FIELDS_REPEATABLE_LABEL="Repeatable (%s)" -PLG_FIELDS_REPEATABLE_PARAMS_FIELDNAME_NAME_LABEL="Name" -PLG_FIELDS_REPEATABLE_PARAMS_FIELDNAME_TYPE_EDITOR="Editor" -PLG_FIELDS_REPEATABLE_PARAMS_FIELDNAME_TYPE_LABEL="Type" -PLG_FIELDS_REPEATABLE_PARAMS_FIELDNAME_TYPE_MEDIA="Media" -PLG_FIELDS_REPEATABLE_PARAMS_FIELDNAME_TYPE_NUMBER="Number" -PLG_FIELDS_REPEATABLE_PARAMS_FIELDNAME_TYPE_TEXT="Text" -PLG_FIELDS_REPEATABLE_PARAMS_FIELDNAME_TYPE_TEXTAREA="Text Area" -PLG_FIELDS_REPEATABLE_PARAMS_FIELDS_LABEL="Form Fields" -PLG_FIELDS_REPEATABLE_XML_DESCRIPTION="Plugin to create a repeatable form with customizable fields." diff --git a/administrator/language/en-GB/plg_fields_repeatable.sys.ini b/administrator/language/en-GB/plg_fields_repeatable.sys.ini deleted file mode 100644 index d61304e05617e..0000000000000 --- a/administrator/language/en-GB/plg_fields_repeatable.sys.ini +++ /dev/null @@ -1,7 +0,0 @@ -; Joomla! Project -; Copyright (C) 2005 - 2019 Open Source Matters. All rights reserved. -; License GNU General Public License version 2 or later; see LICENSE.txt -; Note : All ini files need to be saved as UTF-8 - -PLG_FIELDS_REPEATABLE="Fields - Repeatable" -PLG_FIELDS_REPEATABLE_XML_DESCRIPTION="Plugin to create a repeatable form with customisable fields." diff --git a/installation/sql/mysql/joomla.sql b/installation/sql/mysql/joomla.sql index 2836697b0edce..e92bc450f6baf 100644 --- a/installation/sql/mysql/joomla.sql +++ b/installation/sql/mysql/joomla.sql @@ -673,7 +673,6 @@ INSERT INTO `#__extensions` (`package_id`, `name`, `type`, `element`, `folder`, (0, 'plg_editors-xtd_fields', 'plugin', 'fields', 'editors-xtd', 0, 1, 1, 0, '', '', 0, NULL, 0, 0), (0, 'plg_sampledata_blog', 'plugin', 'blog', 'sampledata', 0, 1, 1, 0, '', '', 0, NULL, 0, 0), (0, 'plg_system_sessiongc', 'plugin', 'sessiongc', 'system', 0, 1, 1, 0, '', '', 0, NULL, 0, 0), -(0, 'plg_fields_repeatable', 'plugin', 'repeatable', 'fields', 0, 1, 1, 0, '', '', 0, NULL, 0, 0), (0, 'plg_content_confirmconsent', 'plugin', 'confirmconsent', 'content', 0, 0, 1, 0, '', '{}', 0, NULL, 0, 0), (0, 'plg_system_actionlogs', 'plugin', 'actionlogs', 'system', 0, 1, 1, 0, '', '{}', 0, NULL, 0, 0), (0, 'plg_actionlog_joomla', 'plugin', 'joomla', 'actionlog', 0, 1, 1, 0, '', '{}', 0, NULL, 0, 0), diff --git a/installation/sql/postgresql/joomla.sql b/installation/sql/postgresql/joomla.sql index 28cba57814a00..d978708a3787d 100644 --- a/installation/sql/postgresql/joomla.sql +++ b/installation/sql/postgresql/joomla.sql @@ -684,7 +684,6 @@ INSERT INTO "#__extensions" ("package_id", "name", "type", "element", "folder", (0, 'plg_editors-xtd_fields', 'plugin', 'fields', 'editors-xtd', 0, 1, 1, 0, '', '', 0, NULL, 0, 0), (0, 'plg_sampledata_blog', 'plugin', 'blog', 'sampledata', 0, 1, 1, 0, '', '', 0, NULL, 0, 0), (0, 'plg_system_sessiongc', 'plugin', 'sessiongc', 'system', 0, 1, 1, 0, '', '', 0, NULL, 0, 0), -(0, 'plg_fields_repeatable', 'plugin', 'repeatable', 'fields', 0, 1, 1, 0, '', '', 0, NULL, 0, 0), (0, 'plg_content_confirmconsent', 'plugin', 'confirmconsent', 'content', 0, 0, 1, 0, '', '{}', 0, NULL, 0, 0), (0, 'plg_system_actionlogs', 'plugin', 'actionlogs', 'system', 0, 1, 1, 0, '', '{}', 0, NULL, 0, 0), (0, 'plg_actionlog_joomla', 'plugin', 'joomla', 'actionlog', 0, 1, 1, 0, '', '{}', 0, NULL, 0, 0), diff --git a/plugins/fields/repeatable/params/repeatable.xml b/plugins/fields/repeatable/params/repeatable.xml deleted file mode 100644 index 490b1e9b11224..0000000000000 --- a/plugins/fields/repeatable/params/repeatable.xml +++ /dev/null @@ -1,67 +0,0 @@ - -
diff --git a/plugins/fields/repeatable/repeatable.php b/plugins/fields/repeatable/repeatable.php deleted file mode 100644 index f37974ee7f78c..0000000000000 --- a/plugins/fields/repeatable/repeatable.php +++ /dev/null @@ -1,107 +0,0 @@ -app->isClient('api')) - { - return; - } - - // Check if the field should be processed by us - if (!$this->isTypeSupported($field->type)) - { - return; - } - - $field->apivalue = (array) json_decode($field->value, true); - } - - /** - * Transforms the field into a DOM XML element and appends it as a child on the given parent. - * - * @param stdClass $field The field. - * @param DOMElement $parent The field node parent. - * @param Form $form The form. - * - * @return DOMElement - * - * @since 3.9.0 - */ - public function onCustomFieldsPrepareDom($field, DOMElement $parent, Form $form) - { - $fieldNode = parent::onCustomFieldsPrepareDom($field, $parent, $form); - - if (!$fieldNode) - { - return $fieldNode; - } - - $readonly = false; - - if (!FieldsHelper::canEditFieldValue($field)) - { - $readonly = true; - } - - $fieldNode->setAttribute('type', 'subform'); - $fieldNode->setAttribute('multiple', 'true'); - $fieldNode->setAttribute('layout', 'joomla.form.field.subform.repeatable-table'); - - // Build the form source - $fieldsXml = new SimpleXMLElement('