Skip to content

Conversation

@Harmageddon
Copy link
Contributor

@Harmageddon Harmageddon commented Feb 22, 2017

This pull request introduces the option to both filter and validate subforms field by field. The intention is that every single field inside a subform can have its own filter and validation parameters. Before this PR, these parameters would just be ignored.

Summary of Changes

  • Introduced a Parameter filter="subform" to filter a subform field by filtering its child fields individually.
  • Introduced a Parameter validate="subform" to validate a subform field by validating its child fields individually.
  • Moved the loading of the JForm instance and its data inside the subform field from the getInput method into two separate methods to avoid repeated code, because the subform has to be loaded for filtering and validation, too. This change should not change any present behavior of subform fields, because I only moved the existing code into two additional methods.

Testing Instructions

  • Test with a form containing a subform field.
    • I created a small module for testing purposes on mod_demovalidation. It has no frontend, only the form in the module configuration in backend. Feel free to use this, but I'd recommend that you play around with different parameter combinations to not just replicate my tests.
    • You can also use the testing instructions from New field Subform, and deprecate the Repeatable field #7829 and add some filters and validations...
    • ... or those from Support inline subforms #12813, where an inline subform is used.
    • If you are developing an extension using subforms, this is the perfect opportunity to improve it! ;-)
  • Test the impact of filters.
    • Add filter="subform" to the subform field.
    • For example, add filter="int" to a field inside the subform XML and try to save values like "abc" (should be filtered to 0) or "0002" (should be filtered to 2).
    • Make sure that correct values are not filtered and everything is stored correctly.
  • Test the impact of validation.
    • Add validate="subform" to the subform field.
    • For example, add validate="email" to a field inside the subform XML and try to save values like "foo".
    • Make sure that correct values are not marked as invalid and everything is stored correctly.
  • Make sure that the subform field behaves like before (apart from the added filtering and validation options).
  • Test everything with multiple="true", multiple="false" and without the multiple parameter for the subform field.

Expected result

  • Unfiltered values should be filtered, depending on the filters of the fields inside the subform XML.
  • Invalid values should be rejected, depending on the validation rules of the fields inside the subform XML.

If a validation fails, there are three options what to display as an error message:

  1. "Invalid Field: Subform" (Title of the subform field containing at least one invalid value)
  2. "Invalid Field: Mail" (Title of the first invalid single field inside the subform)
  3. Like 2., but for all invalid fields.

I used option 2, with a fallback to 1. For a subform containing invalid values, only one warning message will be returned, containing the title of the first field with an invalid value. I'm preferring this over option 1, because it is more specific about where the error is. Option 3 would be possible using additional messages, but as the basic implementation of validation itself displays a maximum of 3 messages, I thought it would be sufficient to send only one here.

Documentation Changes Required

  • As I have no experience in the version numbering of the CMS libraries, I'd be thankful if someone could tell me which value should be added for the @since tag for the JFormRuleSubform class.
  • When the pull request is merged and released, I will add short documentation about the additional parameters on docs.joomla.org.

@zero-24
Copy link
Contributor

zero-24 commented Feb 22, 2017

@Harmageddon with the last 3 commit i have just fixed a error from drone and minimal cs ;)

@Harmageddon
Copy link
Contributor Author

Harmageddon commented Feb 22, 2017

@zero-24 Awesome, thank you! Didn't know about this macro, very useful!

@Harmageddon
Copy link
Contributor Author

Added some small code cleanups, inspired by @matrikular. Thank you!

@laoneo
Copy link
Member

laoneo commented Sep 5, 2017

Nice pr. Can you please fix the merge conflicts that we can test it?

@Harmageddon
Copy link
Contributor Author

@laoneo Thank you! It should work now.

/**
* Loads the form instance for the subform.
*
* @return JForm The form instance.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use the namespaced class? Thanks.


// Prepare the form template
$formname = 'subform.' . ($this->group ? $this->group . '.' : '') . $this->fieldname;
$tmpl = JForm::getInstance($formname, $this->formsource, array('control' => $control));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use the namespaced class? Thanks.

/**
* Binds given data to the subform and its elements.
*
* @param JForm &$subForm Form instance of the subform.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use the namespaced class? Thanks.

*
* @since __DEPLOY_VERSION__
*/
private function loadSubFormData(&$subForm)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you type hint it?

for ($i = 0; $i < $c; $i++)
{
$control = $this->name . '[' . $this->fieldname . $i . ']';
$itemForm = JForm::getInstance($subForm->getName() . $i, $this->formsource, array('control' => $control));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use the namespaced class? Thanks.

*
* @since __DEPLOY_VERSION__
*/
class JFormRuleSubform extends JFormRule
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use the namespaced class? Thanks.

*
* @since __DEPLOY_VERSION__
*/
public function test(SimpleXMLElement $element, $value, $group = null, Registry $input = null, JForm $form = null)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use the namespaced class? Thanks.

* For example if the field has name="foo" and the group value is set to "bar" then the
* full field name would end up being "bar[foo]".
* @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
* @param JForm $form The form object for which the field is being tested.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use the namespaced class? Thanks.

@laoneo
Copy link
Member

laoneo commented Sep 6, 2017

Thanks, added a few comments to use the namespaced classes. If done I will test it.

@Harmageddon
Copy link
Contributor Author

Done. Thanks in advance!

@laoneo
Copy link
Member

laoneo commented Sep 6, 2017

Now you got me wrong, use the real classes like \Joomla\CMS\Form\Form, etc. Not just adding a backslash before them.

@Harmageddon
Copy link
Contributor Author

Oh, sorry. Should I copy the rule to libraries/src/Form/Rule, too, then? Or does it work with this "mixed" form (the subform field and the rule in libraries/joomla/form and the form and the JFormFieldRule class in libraries/src/Form)?

@laoneo
Copy link
Member

laoneo commented Sep 6, 2017

Yes please copy the rule also to libraries/src/Form/Rule. The field has to stay because of autoloading. In Joomla 4 we have moved it also to libraries/src/Form/Field.

@Harmageddon
Copy link
Contributor Author

Okay, a little bit confusing. I hope I haven't messed up anything.

@@ -0,0 +1,88 @@
<?php
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one can be deleted.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The <?php tag? Why?

Copy link
Member

@laoneo laoneo Sep 6, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant the whole file as you have the namespaced one.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see. Seems I have missed that the rules have been moved completely.

* @license GNU General Public License version 2 or later; see LICENSE
*/

use Joomla\CMS\Form\Form;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor cosmetics here, it belongs below the defined check.

@laoneo
Copy link
Member

laoneo commented Sep 6, 2017

I have tested this item ✅ successfully on d379667

It worked. I'v tested it by changing the file plugins/fields/checkboxes/params/checkboxes.xml to

<?xml version="1.0" encoding="utf-8"?>
<form>
	<fields name="fieldparams">
		<fieldset name="fieldparams">
			<field
				name="options"
				type="subform"
				label="PLG_FIELDS_CHECKBOXES_PARAMS_OPTIONS_LABEL"
				description="PLG_FIELDS_CHECKBOXES_PARAMS_OPTIONS_DESC"
				layout="joomla.form.field.subform.repeatable-table"
				icon="list"
				multiple="true"
				filter="subform"
				validate="subform"
			>
				<form hidden="true" name="list_templates_modal" repeat="true">
					<field
						name="name"
						type="text"
						label="PLG_FIELDS_CHECKBOXES_PARAMS_OPTIONS_NAME_LABEL"
						size="30"
					/>

					<field
						name="value"
						type="text"
						label="PLG_FIELDS_CHECKBOXES_PARAMS_OPTIONS_VALUE_LABEL"
						size="30"
						validate="color"
					/>
				</form>
			</field>
		</fieldset>
	</fields>
</form>

After that I created a checkbox field and tried to create an option where the value is not a hex number. It failed. The I changed validate="color"to filter="int" and tried to save again, then it successfully saved it but turned the value into an integer.


This comment was created with the J!Tracker Application at issues.joomla.org/tracker/joomla-cms/14189.

@SharkyKZ
Copy link
Contributor

I have tested this item ✅ successfully on 6b79cea


This comment was created with the J!Tracker Application at issues.joomla.org/tracker/joomla-cms/14189.

@ghost
Copy link

ghost commented May 15, 2018

@laoneo can you please retest?

@ghost
Copy link

ghost commented May 24, 2018

Ready to Commit after two successful tests.

@joomla-cms-bot joomla-cms-bot added the RTC This Pull Request is Ready To Commit label May 24, 2018
@laoneo
Copy link
Member

laoneo commented May 24, 2018

Perhaps you should consider to make your own callback for filter instead of hardcoding the subform filter into the form class. More details can be found here #20569 (comment).

@Harmageddon
Copy link
Contributor Author

Are there any other filters that could serve as an example? I assume that I'd have to create a new class? Where should this class be located? As far as I see, most core rules (like "access") are directly in the Form class.

I would use the method you suggest only for third-party extensions and keep the core filters in one place - either in the Form class as of now or each one in a dedicated class. If we keep some filters in one and others in another place, we risk inconsistencies (i.e. if the filter mechanism gets changed) and people will have a harder time to find all available filters when digging through the code. Personally, I'd support your approach to create classes for filters, because I remember debugging the filter method until I found the hardcoded filters in the Form class. But I'd prefer to either move all or none.

@mbabker
Copy link
Contributor

mbabker commented May 24, 2018

What is hardcoded into the Form class' filterField method is really a bunch of special cases specific to the form data that don't belong to the InputFilter class. So, "core filters" don't need to be hardcoded into the Form class itself unless there is a strong reason why it must be part of the Form API and available at all times.

@Harmageddon
Copy link
Contributor Author

Okay... So is there any best practice where else to put such a filter? And if I see this right, we'd then require users/developers to use a filter="SomeHelperClass:subform" instead of just filter="subform"?

@SharkyKZ
Copy link
Contributor

IMO, form field filtering and validation should be implicit whenever possible to reduce XML form maintenance needs.

So Form's validateField and filterField logic could be moved to FormField. Subform field could then override parent methods and other fields could declare default filters and validation rules to be used when none is added in XML.

Here's a mockup based on your code and #12414 SharkyKZ@fa514ae.

@mbabker
Copy link
Contributor

mbabker commented May 24, 2018

So Form's validateField and filterField logic could be moved to FormField. Subform field could then override parent methods and other fields could declare default filters and validation rules to be used when none is added in XML.

Not necessarily, because each part of the API has a separate concern.

FormField is in essence the visual representation of the field and handles the rendering, the class chain does not belong in the filtering/validation process at all.

FormRule defines some validation rules to be applied when validating the form's data, these are called during the validation process only.

Form is the data manager for a form instance and this is where the filtering and validation processes belong.

@SharkyKZ
Copy link
Contributor

That's the way it is now but is it really correct?

FormField is in essence the visual representation of the field and handles the rendering, the class chain does not belong in the filtering/validation process at all.

Form fields also provide the data options and data type. So filtering and validation makes sense here. Filtering and validation is applied per-field anyways. That said, shouldn't FormRule be FormFieldRule in the first place?

List-based fields should be validated against options (except when custom option is allowed), numeric fields should be filtered accordingly and Subform should not accept other filters but its own (currently, adding a filter like string to subform field causes fatal error).

I think this functionality is vital and should be implemented one way or another.

@mbabker
Copy link
Contributor

mbabker commented May 25, 2018

Rewrite the entire form package from scratch then? There is already a separation of concern regarding each of the object types in the package and an established pattern of use, if you feel this is inefficient feel free to propose a new package but the existing class chain can't just be changed out of the blue like this. By design, filtering and validation are not the responsibility of the FormField classes and refactoring to move responsibilities like this is no small thing. At the end of the day though, regardless of the class structure, what is responsible for rendering should be a different aspect of the API than what is responsible for data validation (if you look at any proper framework you'll find they have a standalone validation component that the form data is pushed through whereas the closest thing we have to that is the FormRule class and that is tightly coupled to the Form class) and if we're going to start talking refactoring the form API then I don't think we should be making one sort of "super" object that does all the things.

@roland-d
Copy link
Contributor

@mbabker Is this something for 3.9?

@Harmageddon
Copy link
Contributor Author

So with this PR being marked as RTC for almost 4 months now, I'd like to ask what is the status for this one? Is it going to be merged for some future release in the current state or do you prefer me to move the filter to some different location for architectural reasons?
@mbabker @wilsonge (pinging you as the release leads for 3.9/3.10)

@mbabker
Copy link
Contributor

mbabker commented Sep 14, 2018

I'm hesitant to merge this because I don't think it is architecturally done correctly and inherently once it ships changing the API will never happen.

@Harmageddon
Copy link
Contributor Author

I see. However, the problem with moving this method outside the Form class is, that we need the form field ($element) in order to load the subform and get the filters for the respective fields inside the subform. Callback filters as well as the InputFilter->clean function only get the value of the field as an argument, but not the whole field.
So a callback filter would get an array containing the values of the fields inside the subform without knowing which value to treat with which filter.

@Harmageddon
Copy link
Contributor Author

Merged staging to include the changes from #17552.

@mbabker With the current API, I don't see any alternative to this implementation.

@Harmageddon Harmageddon closed this Oct 3, 2018
@joomla-cms-bot joomla-cms-bot removed the RTC This Pull Request is Ready To Commit label Oct 3, 2018
@Harmageddon Harmageddon reopened this Oct 3, 2018
@Harmageddon
Copy link
Contributor Author

(sorry, I accidentally hit the wrong button)


This comment was created with the J!Tracker Application at issues.joomla.org/tracker/joomla-cms/14189.

@joomla-cms-bot joomla-cms-bot added the RTC This Pull Request is Ready To Commit label Oct 3, 2018
@Hackwar
Copy link
Member

Hackwar commented Jan 18, 2019

Please see #12414. That might have an impact on this PR.

@Harmageddon
Copy link
Contributor Author

Thank you for your work there! It does indeed have an impact. I'm going to rewrite this PR for 4.0 then. And as this PR here probably won't be ever merged into 3.x after being RTC for 8 months, I'm now closing it and hope for better luck and API integration for 4.0.

@joomla-cms-bot joomla-cms-bot removed the RTC This Pull Request is Ready To Commit label Jan 18, 2019
@wilsonge
Copy link
Contributor

Just for the record I'd definitely be interested in seeing this for Joomla 4!

@Harmageddon
Copy link
Contributor Author

Apparently, the changes proposed in this PR have been added in b06019e and d34a3ec, so I assume they're going to be merged into 4.0, too, and there is no need for a further PR?
@HLeithner / @wilsonge what do you think?

@zero-24
Copy link
Contributor

zero-24 commented Aug 31, 2019

Correct. All 3.x changes are merged up to 4.0 before the final release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants