diff --git a/administrator/components/com_admin/sql/updates/mysql/3.8.0-2017-07-13.sql b/administrator/components/com_admin/sql/updates/mysql/3.8.0-2017-07-13.sql new file mode 100644 index 0000000000000..6878001cd38f9 --- /dev/null +++ b/administrator/components/com_admin/sql/updates/mysql/3.8.0-2017-07-13.sql @@ -0,0 +1,2 @@ +INSERT INTO `#__extensions` (`extension_id`, `package_id`, `name`, `type`, `element`, `folder`, `client_id`, `enabled`, `access`, `protected`, `manifest_cache`, `params`, `custom_data`, `system_data`, `checked_out`, `checked_out_time`, `ordering`, `state`) VALUES +(479, 0, 'plg_fields_subform', 'plugin', 'subform', 'fields', 0, 1, 1, 0, '', '', '', '', 0, '0000-00-00 00:00:00', 0, 0); diff --git a/administrator/components/com_admin/sql/updates/postgresql/3.8.0-2017-07-13.sql b/administrator/components/com_admin/sql/updates/postgresql/3.8.0-2017-07-13.sql new file mode 100644 index 0000000000000..36a4d97443329 --- /dev/null +++ b/administrator/components/com_admin/sql/updates/postgresql/3.8.0-2017-07-13.sql @@ -0,0 +1,2 @@ +INSERT INTO "#__extensions" ("extension_id", "package_id", "name", "type", "element", "folder", "client_id", "enabled", "access", "protected", "manifest_cache", "params", "custom_data", "system_data", "checked_out", "checked_out_time", "ordering", "state") VALUES +(479, 0, 'plg_fields_subform', 'plugin', 'subform', 'fields', 0, 1, 1, 0, '', '', '', '', 0, '1970-01-01 00:00:00', 0, 0); diff --git a/administrator/components/com_admin/sql/updates/sqlazure/3.8.0-2017-07-13.sql b/administrator/components/com_admin/sql/updates/sqlazure/3.8.0-2017-07-13.sql new file mode 100644 index 0000000000000..9c66a71f75168 --- /dev/null +++ b/administrator/components/com_admin/sql/updates/sqlazure/3.8.0-2017-07-13.sql @@ -0,0 +1,2 @@ +INSERT INTO "#__extensions" ("extension_id", "package_id", "name", "type", "element", "folder", "client_id", "enabled", "access", "protected", "manifest_cache", "params", "custom_data", "system_data", "checked_out", "checked_out_time", "ordering", "state") VALUES +(479, 0, 'plg_fields_subform', 'plugin', 'subform', 'fields', 0, 1, 1, 0, '', '', '', '', 0, '1900-01-01 00:00:00', 0, 0); diff --git a/administrator/components/com_fields/libraries/fieldsplugin.php b/administrator/components/com_fields/libraries/fieldsplugin.php index b545a791a93c1..aeb627c866cde 100644 --- a/administrator/components/com_fields/libraries/fieldsplugin.php +++ b/administrator/components/com_fields/libraries/fieldsplugin.php @@ -220,11 +220,32 @@ public function onCustomFieldsPrepareDom($field, DOMElement $parent, JForm $form * @since 3.7.0 */ public function onContentPrepareForm(JForm $form, $data) + { + $path = $this->getFormPath($form, $data); + if ($path === null) + { + return; + } + // Load the specific plugin parameters + $form->load(file_get_contents($path), true, '/form/*'); + } + + /** + * Returns the path of the XML definition file for the field parameters + * + * @param JForm $form The form + * @param stdClass $data The data + * + * @return string + * + * @since __DEPLOY_VERSION__ + */ + protected function getFormPath(JForm $form, $data) { // Check if the field form is calling us if (strpos($form->getName(), 'com_fields.field') !== 0) { - return; + return null; } // Ensure it is an object @@ -241,7 +262,7 @@ public function onContentPrepareForm(JForm $form, $data) // Not us if (!$this->isTypeSupported($type)) { - return; + return null; } $path = JPATH_PLUGINS . '/' . $this->_type . '/' . $this->_name . '/params/' . $type . '.xml'; @@ -249,11 +270,9 @@ public function onContentPrepareForm(JForm $form, $data) // Check if params file exists if (!file_exists($path)) { - return; + return null; } - - // Load the specific plugin parameters - $form->load(file_get_contents($path), true, '/form/*'); + return $path; } /** diff --git a/administrator/components/com_fields/models/fields/subformtype.php b/administrator/components/com_fields/models/fields/subformtype.php new file mode 100644 index 0000000000000..d59c1c6ef41ee --- /dev/null +++ b/administrator/components/com_fields/models/fields/subformtype.php @@ -0,0 +1,57 @@ +text, $b->text); + } + ); + + return $options; + } +} diff --git a/administrator/language/en-GB/en-GB.plg_fields_subform.ini b/administrator/language/en-GB/en-GB.plg_fields_subform.ini new file mode 100644 index 0000000000000..9987d21a3e799 --- /dev/null +++ b/administrator/language/en-GB/en-GB.plg_fields_subform.ini @@ -0,0 +1,16 @@ +; Joomla! Project +; Copyright (C) 2005 - 2017 Open Source Matters. All rights reserved. +; License GNU General Public License version 2 or later; see LICENSE.txt, see LICENSE.php +; Note : All ini files need to be saved as UTF-8 + +PLG_FIELDS_SUBFORM="Fields - Subform" +PLG_FIELDS_SUBFORM_XML_DESCRIPTION="This plugin lets you create new fields of type 'subform' in any extensions where custom fields are supported." +PLG_FIELDS_SUBFORM_LABEL="Subform (%s)" +PLG_FIELDS_SUBFORM_PARAMS_OPTIONS_LABEL="Fields" +PLG_FIELDS_SUBFORM_PARAMS_OPTIONS_DESC="The fields of the subform." +PLG_FIELDS_SUBFORM_PARAMS_REPEAT_LABEL="Repeatable" +PLG_FIELDS_SUBFORM_PARAMS_REPEAT_DESC="Whether this subform shall be repeatable." +PLG_FIELDS_SUBFORM_PARAMS_RENDER_VALUES_LABEL="Render values" +PLG_FIELDS_SUBFORM_PARAMS_RENDER_VALUES_DESC="Do you want the custom field plugin to render the values of this subforms fields? Rendering is, for example, unneeded if your subform only contains 'text' fields, because text values does't change when being rendered. But when you e.g. have a 'media' field, the unrendered value will be the url to that media, the rendered value will be a HTML <img>-tag, for example. If you create your own layout override, where you can take care of the unrendered value, you should always disable rendering, as rendering always comes at a cost of performance." +PLG_FIELDS_SUBFORM_PARAMS_OPTIONS_NAME_LABEL="Name" +PLG_FIELDS_SUBFORM_PARAMS_OPTIONS_NAME_DESC="The name to identify the field." diff --git a/administrator/language/en-GB/en-GB.plg_fields_subform.sys.ini b/administrator/language/en-GB/en-GB.plg_fields_subform.sys.ini new file mode 100644 index 0000000000000..53a7cc0430dec --- /dev/null +++ b/administrator/language/en-GB/en-GB.plg_fields_subform.sys.ini @@ -0,0 +1,7 @@ +; Joomla! Project +; Copyright (C) 2005 - 2017 Open Source Matters. All rights reserved. +; License GNU General Public License version 2 or later; see LICENSE.txt, see LICENSE.php +; Note : All ini files need to be saved as UTF-8 + +PLG_FIELDS_SUBFORM="Fields - Subform" +PLG_FIELDS_SUBFORM_XML_DESCRIPTION="This plugin lets you create new fields of type 'subform' in any extensions where custom fields are supported." diff --git a/installation/sql/mysql/joomla.sql b/installation/sql/mysql/joomla.sql index 9c5d6ca57481d..5da90f1d1bf06 100644 --- a/installation/sql/mysql/joomla.sql +++ b/installation/sql/mysql/joomla.sql @@ -667,6 +667,7 @@ INSERT INTO `#__extensions` (`extension_id`, `package_id`, `name`, `type`, `elem (492, 0, 'plg_privacy_message', 'plugin', 'message', 'privacy', 0, 1, 1, 0, '', '{}', '', '', 0, '0000-00-00 00:00:00', 0, 0), (493, 0, 'plg_privacy_actionlogs', 'plugin', 'actionlogs', 'privacy', 0, 0, 1, 0, '', '{}', '', '', 0, '0000-00-00 00:00:00', 0, 0), (494, 0, 'plg_captcha_recaptcha_invisible', 'plugin', 'recaptcha_invisible', 'captcha', 0, 0, 1, 0, '', '{"public_key":"","private_key":"","theme":"clean"}', '', '', 0, '0000-00-00 00:00:00', 0, 0), +(495, 0, 'plg_fields_subform', 'plugin', 'subform', 'fields', 0, 1, 1, 0, '', '', '', '', 0, '0000-00-00 00:00:00', 0, 0), (503, 0, 'beez3', 'template', 'beez3', '', 0, 1, 1, 0, '', '{"wrapperSmall":"53","wrapperLarge":"72","sitetitle":"","sitedescription":"","navposition":"center","templatecolor":"nature"}', '', '', 0, '0000-00-00 00:00:00', 0, 0), (504, 0, 'hathor', 'template', 'hathor', '', 1, 1, 1, 0, '', '{"showSiteName":"0","colourChoice":"0","boldText":"0"}', '', '', 0, '0000-00-00 00:00:00', 0, 0), (506, 0, 'protostar', 'template', 'protostar', '', 0, 1, 1, 0, '', '{"templateColor":"","logoFile":"","googleFont":"1","googleFontName":"Open+Sans","fluidContainer":"0"}', '', '', 0, '0000-00-00 00:00:00', 0, 0), diff --git a/installation/sql/postgresql/joomla.sql b/installation/sql/postgresql/joomla.sql index 58b3530ac4b38..e8096e249319f 100644 --- a/installation/sql/postgresql/joomla.sql +++ b/installation/sql/postgresql/joomla.sql @@ -680,6 +680,7 @@ INSERT INTO "#__extensions" ("extension_id", "package_id", "name", "type", "elem (492, 0, 'plg_privacy_message', 'plugin', 'message', 'privacy', 0, 1, 1, 0, '', '{}', '', '', 0, '1970-01-01 00:00:00', 0, 0), (493, 0, 'plg_privacy_actionlogs', 'plugin', 'actionlogs', 'privacy', 0, 0, 1, 0, '', '{}', '', '', 0, '1970-01-01 00:00:00', 0, 0), (494, 0, 'plg_captcha_recaptcha_invisible', 'plugin', 'recaptcha_invisible', 'captcha', 0, 0, 1, 0, '', '{"public_key":"","private_key":"","theme":"clean"}', '', '', 0, '1970-01-01 00:00:00', 0, 0), +(495, 0, 'plg_fields_subform', 'plugin', 'subform', 'fields', 0, 1, 1, 0, '', '', '', '', 0, '1970-01-01 00:00:00', 0, 0), (503, 0, 'beez3', 'template', 'beez3', '', 0, 1, 1, 0, '', '{"wrapperSmall":"53","wrapperLarge":"72","sitetitle":"","sitedescription":"","navposition":"center","templatecolor":"nature"}', '', '', 0, '1970-01-01 00:00:00', 0, 0), (504, 0, 'hathor', 'template', 'hathor', '', 1, 1, 1, 0, '', '{"showSiteName":"0","colourChoice":"0","boldText":"0"}', '', '', 0, '1970-01-01 00:00:00', 0, 0), (506, 0, 'protostar', 'template', 'protostar', '', 0, 1, 1, 0, '', '{"templateColor":"","logoFile":"","googleFont":"1","googleFontName":"Open+Sans","fluidContainer":"0"}', '', '', 0, '1970-01-01 00:00:00', 0, 0), diff --git a/installation/sql/sqlazure/joomla.sql b/installation/sql/sqlazure/joomla.sql index 02717e9663390..a5776351030a3 100644 --- a/installation/sql/sqlazure/joomla.sql +++ b/installation/sql/sqlazure/joomla.sql @@ -895,6 +895,7 @@ INSERT INTO "#__extensions" ("extension_id", "package_id", "name", "type", "elem (492, 0, 'plg_privacy_message', 'plugin', 'user', 'message', 0, 1, 1, 0, '', '{}', '', '', 0, '1900-01-01 00:00:00', 0, 0), (493, 0, 'plg_privacy_actionlogs', 'plugin', 'actionlogs', 'privacy', 0, 0, 1, 0, '', '{}', '', '', 0, '1900-01-01 00:00:00', 0, 0), (494, 0, 'plg_captcha_recaptcha_invisible', 'plugin', 'recaptcha_invisible', 'captcha', 0, 0, 1, 0, '', '{"public_key":"","private_key":"","theme":"clean"}', '', '', 0, '1900-01-01 00:00:00', 0, 0), +(495, 0, 'plg_fields_subform', 'plugin', 'subform', 'fields', 0, 1, 1, 0, '', '', '', '', 0, '1900-01-01 00:00:00', 0, 0), (503, 0, 'beez3', 'template', 'beez3', '', 0, 1, 1, 0, '', '{"wrapperSmall":"53","wrapperLarge":"72","sitetitle":"","sitedescription":"","navposition":"center","templatecolor":"nature"}', '', '', 0, '1900-01-01 00:00:00', 0, 0), (504, 0, 'hathor', 'template', 'hathor', '', 1, 1, 1, 0, '', '{"showSiteName":"0","colourChoice":"0","boldText":"0"}', '', '', 0, '1900-01-01 00:00:00', 0, 0), (506, 0, 'protostar', 'template', 'protostar', '', 0, 1, 1, 0, '', '{"templateColor":"","logoFile":"","googleFont":"1","googleFontName":"Open+Sans","fluidContainer":"0"}', '', '', 0, '1900-01-01 00:00:00', 0, 0), diff --git a/media/system/js/subform-repeatable.js b/media/system/js/subform-repeatable.js index 65965ede4666a..f5b80758d832b 100644 --- a/media/system/js/subform-repeatable.js +++ b/media/system/js/subform-repeatable.js @@ -1 +1 @@ -!function(R){"use strict";R.subformRepeatable=function(e,t){if(this.$container=R(e),this.$container.data("subformRepeatable"))return a;this.$container.data("subformRepeatable",a),this.options=R.extend({},R.subformRepeatable.defaults,t),this.template="",this.prepareTemplate(),this.$containerRows=this.options.rowsContainer?this.$container.find(this.options.rowsContainer):this.$container,this.lastRowNum=this.$containerRows.find(this.options.repeatableElement).length;var a=this;this.$container.on("click",this.options.btAdd,function(e){e.preventDefault();var t=R(this).parents(a.options.repeatableElement);t.length||(t=null),a.addRow(t)}),this.$container.on("click",this.options.btRemove,function(e){e.preventDefault();var t=R(this).parents(a.options.repeatableElement);a.removeRow(t)}),this.options.btMove&&this.$containerRows.sortable({items:this.options.repeatableElement,handle:this.options.btMove,tolerance:"pointer"}),this.$container.trigger("subform-ready")},R.subformRepeatable.prototype.prepareTemplate=function(){if(this.options.rowTemplateSelector)this.template=R.trim(this.$container.find(this.options.rowTemplateSelector).last().html())||"";else{var e=this.$container.find(this.options.repeatableElement).get(0),t=R(e).clone();try{this.clearScripts(t)}catch(e){window.console&&console.log(e)}this.template=t.prop("outerHTML")}},R.subformRepeatable.prototype.addRow=function(e){var t=this.$containerRows.find(this.options.repeatableElement).length;if(t>=this.options.maximum)return null;var a=R.parseHTML(this.template);e?R(e).after(a):this.$containerRows.append(a);var o=R(a);o.attr("data-new","true"),this.fixUniqueAttributes(o,t);try{this.fixScripts(o)}catch(e){window.console&&console.log(e)}return this.$container.trigger("subform-row-add",o),o},R.subformRepeatable.prototype.removeRow=function(e){this.$containerRows.find(this.options.repeatableElement).length<=this.options.minimum||(this.$container.trigger("subform-row-remove",e),e.remove())},R.subformRepeatable.prototype.fixUniqueAttributes=function(e,t,a,o){a=void 0===a?e.attr("data-group"):a,o=void 0===o?e.attr("data-base-name"):o,t=void 0===t?0:t;var i=Math.max(this.lastRowNum,t),r=o+i;this.lastRowNum=i+1,e.attr("data-group",r);for(var n=e.find("[name]"),s={},l=0,p=n.length;l=this.options.maximum)return null;var a=y.parseHTML(this.template);e?y(e).after(a):this.$containerRows.append(a);var o=y(a);o.attr("data-new","true"),this.fixUniqueAttributes(o,t);try{this.fixScripts(o)}catch(e){window.console&&console.log(e)}return this.$container.trigger("subform-row-add",o),o},y.subformRepeatable.prototype.removeRow=function(e){this.$containerRows.find(this.options.repeatableElement).length<=this.options.minimum||(this.$container.trigger("subform-row-remove",e),e.remove())},y.subformRepeatable.prototype.fixUniqueAttributes=function(e,t,a,o){var i=void 0===a?e.attr("data-group"):a,r=void 0===o?e.attr("data-base-name"):o,n=void 0===t?0:t,s=Math.max(this.lastRowNum,n),l=r+s;this.lastRowNum=s+1,e.attr("data-group",l);for(var p=e.find("[name]"),f={},c=0,d=p.length;c +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ diff --git a/plugins/fields/subform/subform.php b/plugins/fields/subform/subform.php new file mode 100644 index 0000000000000..d583328a735d3 --- /dev/null +++ b/plugins/fields/subform/subform.php @@ -0,0 +1,424 @@ +getFormPath($form, $data); + if ($path === null) + { + return; + } + + // Load our own form definition + $xml = new DOMDocument(); + $xml->load($path); + + // Get the options subform + $xmlxpath = new DOMXPath($xml); + $hiddenform = $xmlxpath->evaluate( + '/form/fields[@name="fieldparams"]/fieldset[@name="fieldparams"]/field[@name="options"]/form' + ); + if ($hiddenform->length != 1) + { + // Something is wrong, abort. + return; + } + $hiddenform = $hiddenform->item(0); + + // Iterate over all fields which we know (all our wanted subfields). + $fieldTypes = FieldsHelper::getFieldTypes(); + foreach ($fieldTypes as $fieldType) + { + // Skip subform type, we dont want to allow subforms in subforms + // (to ease complexity) + if ($fieldType['type'] == 'subform') + { + continue; + } + // Check whether the XML definition file for that type exists + $path = (JPATH_PLUGINS . '/' . $this->_type . '/' . $fieldType['type'] . '/params/' . $fieldType['type'] . '.xml'); + if (!file_exists($path)) + { + continue; + } + + try + { + // Try to load the XML definition file into a DOMDocument + $subxml = new DOMDocument(); + $subxml->load($path); + $subxmlxpath = new DOMXPath($subxml); + + // XPath all fields from that XML document + $fields = $subxmlxpath->evaluate('/form/fields[@name="fieldparams"]/fieldset[@name="fieldparams"]/field'); + for ($i = 0; $i < $fields->length; $i++) + { + $field = $fields->item($i); + /* @var $field \DOMElement */ + + // Rewrite this fields name, e.g. rewrite name=='buttons' for + // the editor fieldtype to '_type-editor_buttons' + $this->rewriteNodeNameRecursive( + $field, + ('_type-' . $fieldType['type'] . '_') + ); + + // Only show this field when the field 'type' is of the specific type + $field->setAttribute('showon', 'type:' . $fieldType['type']); + + // Those cannot be required, the 'showon' does only control visibility, and hence + // invisible elements would be required else + $field->setAttribute('required', '0'); + + // Import the rewritten field into our parent form + $hiddenform->appendChild($xml->importNode($field, true)); + } + } + catch (Exception $e) + { + // Ignore this type for now. + } + } + + // And finally load the form into the JForm + $form->load($xml->saveXML(), true, '/form/*'); + } + + /** + * Manipulates the $field->value before the field is being passed to + * onCustomFieldsPrepareField. + * + * @param string $context + * @param object $item + * @param \stdClass $field + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function onCustomFieldsBeforePrepareField($context, $item, $field) + { + // Check if the field should be processed by us + if (!$this->isTypeSupported($field->type)) + { + return; + } + + $decoded_value = json_decode($field->value, true); + if (!$decoded_value || !is_array($decoded_value)) + { + return; + } + + $field->value = $decoded_value; + } + + /** + * Renders this fields value by rendering all subfields of this subform + * and joining all those rendered subfields. Additionally stores the value + * and raw value of all rendered subfields into $field->subfield_rows. + * + * @param string $context + * @param object $item + * @param \stdClass $field + * + * @return string + * + * @since __DEPLOY_VERSION__ + */ + public function onCustomFieldsPrepareField($context, $item, $field) + { + // Check if the field should be processed by us + if (!$this->isTypeSupported($field->type)) + { + return; + } + + // If we dont have any subfields (or values for them), nothing to do. + if (!is_array($field->value) || count($field->value) < 1) + { + return; + } + + // Get the field params + $field_params = $this->getParamsFromField($field); + + /** + * Placeholder to hold all subform rows (if this subform field is repeatable) + * and per array entry a \stdClass object which holds all the rendered values + * of the configured subfields. + */ + $final_values = array(); + /** + * Placeholder to hold all subform rows (if this subform field is repeatable) + * and per array entry a \stdClass object which holds another \stdClass object + * for each configured subfield, which then again holds the rendered value and + * the raw value of each subfield. + */ + $subfield_rows = array(); + + // Create an array with entries being subform forms, and if not repeatable, + // containing only one element. + $rows = $field->value; + if ($field_params->get('repeat', '1') == '0') + { + $rows = array($field->value); + } + // Iterate over each row of the data + foreach ($rows as $row) + { + // The rendered values for this row, indexed by the name of the subfield + $row_values = new \stdClass; + // Holds for all subfields (indexed by their name) for this row their rendered and raw value. + $row_subfields = new \stdClass(); + // For each row, iterate over all the subfields + foreach ($this->getSubfieldsFromField($field) as $_subfield) + { + // Clone this virtual subfield to not interfere with the other rows + $subfield = (clone $_subfield); + // Just to be sure, unset this subfields value (and rawvalue) + $subfield->rawvalue = $subfield->value = ''; + // If we have data for this field in the current row + if (isset($row[$subfield->name])) + { + // Take over the data into our virtual subfield + $subfield->rawvalue = $subfield->value = trim($row[$subfield->name]); + } + + // Do we want to render the value of our fields? + if ($field_params->get('render_values', '1') == '1') + { + // Do we have a rendercache entry for this type? + if (!isset($this->renderCache[$subfield->type])) + { + $this->renderCache[$subfield->type] = array(); + } + + // Lets see if we have a fast in-memory result for this + if (isset($this->renderCache[$subfield->type][$subfield->rawvalue])) + { + $subfield->value = $this->renderCache[$subfield->type][$subfield->rawvalue]; + } + else + { + // Render this virtual subfield + $subfield->value = \JEventDispatcher::getInstance()->trigger( + 'onCustomFieldsPrepareField', + array($context, $item, $subfield) + ); + $this->renderCache[$subfield->type][$subfield->rawvalue] = $subfield->value; + } + if (is_array($subfield->value)) + { + $subfield->value = implode(' ', $subfield->value); + } + } + + // Store this subfields rendered value into our $row_values object + $row_values->{$subfield->name} = $subfield->value; + // Store the value and rawvalue of this subfield into our $row_subfields object + $row_subfields->{$subfield->name} = new \stdClass(); + $row_subfields->{$subfield->name}->value = $subfield->value; + $row_subfields->{$subfield->name}->rawvalue = $subfield->rawvalue; + } + // Store all the rendered subfield values of this row + $final_values[] = $row_values; + // Store all the rendered and raw subfield values of this row + $subfield_rows[] = $row_subfields; + } + /** + * Store all the rendered and raw values of this subfield rows in $field->subfield_rows, + * because we maybe want to be able to have access to the rendered (and raw) + * value of each row and subfield. + */ + $field->subfield_rows = $subfield_rows; + /** + * Store the renderer per-row subfield values in $field->value, which + * will be rendered (combined into one rendered string) by our parent next. + */ + $field->value = $final_values; + + return parent::onCustomFieldsPrepareField($context, $item, $field); + } + + /** + * Returns a DOMElement which is the child of $orig_parent and represents + * the form XML definition for this subform field. + * + * @param \stdClass $field + * @param DOMElement $orig_parent + * @param JForm $form + * + * @return \DOMElement + * + * @since __DEPLOY_VERSION__ + */ + public function onCustomFieldsPrepareDom($field, DOMElement $orig_parent, JForm $form) + { + // Call the onCustomFieldsPrepareDom method on FieldsPlugin + // This will create a new 'field' DOMElement with type=subform + $parent_field = parent::onCustomFieldsPrepareDom($field, $orig_parent, $form); + if (!$parent_field) + { + return $parent_field; + } + + // Get the configured parameters for this subform field + $field_params = $this->getParamsFromField($field); + + // If this subform should be repeatable, set some attributes on the subform element + if ($field_params->get('repeat', '1') == '1') + { + $parent_field->setAttribute('multiple', 'true'); + $parent_field->setAttribute('layout', 'joomla.form.field.subform.repeatable-table'); + } + + // Create a child 'form' DOMElement under the field[type=subform] element. + $parent_fieldset = $parent_field->appendChild(new DOMElement('form')); + $parent_fieldset->setAttribute('hidden', 'true'); + $parent_fieldset->setAttribute('name', ($field->name . '_modal')); + // If this subform should be repeatable, set some attributes on the modal + if ($field_params->get('repeat', '1') == '1') + { + $parent_fieldset->setAttribute('repeat', 'true'); + } + + // Iterate over the configured fields of this subform + foreach ($this->getSubfieldsFromField($field) as $subfield) + { + // Let the relevant plugins do their work and insert the correct + // DOMElement's into our $parent_fieldset. + \JEventDispatcher::getInstance()->trigger( + 'onCustomFieldsPrepareDom', + array($subfield, $parent_fieldset, $form) + ); + } + + return $parent_field; + } + + /** + * Returns an array of all options configured for this field. + * + * @param \stdClass $field + * + * @return \stdClass[] + * + * @since __DEPLOY_VERSION__ + */ + protected function getOptionsFromField(\stdClass $field) + { + $result = array(); + + // Fetch the options from the plugin + $params = $this->getParamsFromField($field); + foreach ($params->get('options', array()) as $option) + { + $result[] = (object) $option; + } + + return $result; + } + + /** + * Returns the configured params for a given subform field. + * + * @param \stdClass $field + * + * @return Joomla\Registry\Registry + * + * @since __DEPLOY_VERSION__ + */ + protected function getParamsFromField(\stdClass $field) + { + $params = clone($this->params); + if (isset($field->fieldparams) && is_object($field->fieldparams)) + { + $params->merge($field->fieldparams); + } + return $params; + } + + /** + * Returns an array of all subfields for this subform field. + * + * @param stdClass $field + * + * @return \stdClass[] + * + * @since __DEPLOY_VERSION__ + */ + protected function getSubfieldsFromField(\stdClass $field) + { + $result = array(); + + foreach ($this->getOptionsFromField($field) as $option) + { + /* @TODO Better solution to this? */ + $subfield = (clone $field); + $subfield->id = null; + $subfield->title = $option->label; + $subfield->name = $option->name; + $subfield->type = $option->type; + $subfield->required = '0'; + $subfield->default_value = $option->default_value; + $subfield->label = $option->label; + $subfield->description = $option->description; + + $result[] = $subfield; + } + + return $result; + } + + /** + * Recursively prefixes the 'name' attribute of $node with $prefix + * + * @param DOMElement $node + * @param string $prefix + * + * @since __DEPLOY_VERSION__ + */ + protected function rewriteNodeNameRecursive(DOMElement $node, $prefix) + { + if ($node->hasAttribute('name')) + { + $node->setAttribute('name', ($prefix . $node->getAttribute('name'))); + } + + foreach ($node->childNodes as $childNode) + { + if ($childNode instanceof DOMElement) + { + $this->rewriteNodeNameRecursive($childNode, $prefix); + } + } + } +} diff --git a/plugins/fields/subform/subform.xml b/plugins/fields/subform/subform.xml new file mode 100644 index 0000000000000..c76ed546216bf --- /dev/null +++ b/plugins/fields/subform/subform.xml @@ -0,0 +1,19 @@ + + + plg_fields_subform + Joomla! Project + June 2017 + Copyright (C) 2005 - 2017 Open Source Matters. All rights reserved. + GNU General Public License version 2 or later; see LICENSE.txt + admin@joomla.org + www.joomla.org + __DEPLOY_VERSION__ + PLG_FIELDS_SUBFORM_XML_DESCRIPTION + + subform.php + + + en-GB.plg_fields_subform.ini + en-GB.plg_fields_subform.sys.ini + + diff --git a/plugins/fields/subform/tmpl/subform.php b/plugins/fields/subform/tmpl/subform.php new file mode 100644 index 0000000000000..ca0259baec50a --- /dev/null +++ b/plugins/fields/subform/tmpl/subform.php @@ -0,0 +1,65 @@ +'; +foreach ($field->value as $row) +{ + echo '
  • '; + $buffer = array(); + foreach ((array)$row as $key => $value) + { + $buffer[] = ($key . ': ' . $value); + } + echo implode(', ', $buffer); + echo '
  • '; +} +echo ''; + +/** + * Sometimes the above is not what you want, because you want access to the + * raw (unrendered) values of all subfields. Then this way could be a possibility: + */ +//echo '
      '; +//foreach ($field->subfield_rows as $subfield_objects) +//{ +// echo '
    • '; +// $buffer = array(); +// foreach (array_keys(get_object_vars($subfield_objects)) as $subfield_name) +// { +// $buffer[] = ( +// $subfield_name . ': ' +// . $subfield_objects->{$subfield_name}->rawvalue +// ); +// } +// echo implode(', ', $buffer); +// echo '
    • '; +//} +//echo '
    '; +/** + * This example maybe looks a bit odd, but the the idea is that you could use + * something like this: + * + *
      + * subfield_rows as $row): ?> + *
    • + * Name: name->rawvalue; ?>
      + * Image: image->value; ?>
      + * etc... + *
    • + * + *
    + * + * This means you have better control over how the output of your subform fields + * really is because you don't rely on the rendered values of the subform fields + * themselves. + */ diff --git a/plugins/system/fields/fields.php b/plugins/system/fields/fields.php index 9838578360769..5a23072f0f8df 100644 --- a/plugins/system/fields/fields.php +++ b/plugins/system/fields/fields.php @@ -140,6 +140,12 @@ public function onContentAfterSave($context, $item, $isNew, $data = array()) $value = json_encode($value); } + // JSON encode the value if it is an array + if (is_array($value)) + { + $value = json_encode($value); + } + // Setting the value for the field and the item $model->setFieldValue($field->id, $item->id, $value); }