Your component is user-friendly. User experience (UX) or user experience is on everyone's lips. If a user enters incorrect data, it's important to you that they get an explanation. This is where we use validation.
In server-side validation, the input submitted by the user is sent to the server and validated using the scripting language. In the case of Joomla, this is PHP. After the validation process on the server side, the feedback is sent back to the client from a new dynamically generated web page. It is safe to validate user input from the server. Malicious attackers have no easy game this way. Client-side scripting languages are easier to trick. Intruders bypass them to send malicious input to the server.
Since both validation methods (server and client) have their own importance, it is recommended to use them simultaneously. Server-side validation is more secure. The client-side one is more user-friendly!
This part covers the server-side validation in Joomla 4.
For impatient people: View the changed program code in the Diff View[^codeberg.org/astrid/j4examplecode/compare/t10...t11a] and copy these changes into your development version.
Step by step
New files
administrator/components/com_foos/ src/Rule/LetterRule.php
The main goal here is not to learn meaningful validation. Rather, I'm showing you how to integrate your rules into Joomla. That's why you see here only a rudimentary example: In the name it is forbidden to insert a number from now on. In concrete terms, this means: Astrid is allowed. Astrid9 is not allowed. For this we create the file LetterRule.php
.
Here in the example I only use the regular expression to be checked in the class
LetterRule.php
. Of course it is possible to integrate complex checks using functions.
administrator/components/com_foos/src/Rule/LetterRule.php
// https://codeberg.org/astrid/j4examplecode/raw/branch/t11a/src/administrator/components/com_foos/src/Rule/LetterRule.php
<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace FooNamespace\Component\Foos\Administrator\Rule;
\defined('_JEXEC') or die;
use Joomla\CMS\Form\FormRule;
/**
* Form Rule class for the Joomla Platform.
*
* @since __DEPLOY_VERSION__
*/
class LetterRule extends FormRule
{
/**
* The regular expression to use in testing a form field value.
*
* @var string
* @since __DEPLOY_VERSION__
*/
protected $regex = '^([a-z]+)$';
/**
* The regular expression modifiers to use when testing a form field value.
*
* @var string
* @since __DEPLOY_VERSION__
*/
protected $modifiers = 'i';
}
It is not necessary to implement the test
method in your file. You inherit it from the class FormRule
, which is implemented in the file /libraries/src/Form/FormRule.php
. In it you will find the code
...
protected $regex;
...
public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
{
...
// Test the value against the regular expression.
if (preg_match(\chr(1) . $this->regex . \chr(1) . $this->modifiers, $value)) {
return true;
}
return false;
}
Modified files
To make Joomla apply the rule in the LetterRule.php
file to the text field for entering the name, we modify the form file.
administrator/components/com_foos/ forms/foo.xml
What has changed is <fieldset addruleprefix="FooNamespace\Component\Foos\Administrator\Rule">
and validate="Letter"
. The parameter addruleprefix="FooNamespace\Component\Foos\Administrator\Rule"
ensures that the form searches for rules in the namespace FooNamespace\Component\Foos\Administrator\Rule
and validate="Letter"
indicates that the rule Letter
, i.e. according to Joomla standard the class LetterRule
, is applied.
administrator/components/com_foos/forms/foo.xml
<?xml version="1.0" encoding="utf-8"?>
<form>
<fieldset>
<fieldset addruleprefix="FooNamespace\Component\Foos\Administrator\Rule">
<field
name="id"
type="number"
<field
name="name"
type="text"
validate="Letter"
label="COM_FOOS_FIELD_NAME_LABEL"
size="40"
required="true"
Example taken from Joomla Core
To show you what is possible I add below two examples from Joomla Core as inspiration.
Username
For the username, the Joomla database is used to check if the name already exists. In this case false
is returned. Otherwise the test is successful.
<?php
namespace Joomla\CMS\Form\Rule;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormRule;
use Joomla\Database\DatabaseAwareInterface;
use Joomla\Database\DatabaseAwareTrait;
use Joomla\Database\ParameterType;
use Joomla\Registry\Registry;
class UsernameRule extends FormRule implements DatabaseAwareInterface
{
use DatabaseAwareTrait;
public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
{
// Get the database object and a new query object.
$db = $this->getDatabase();
$query = $db->getQuery(true);
// Get the extra field check attribute.
$userId = ($form instanceof Form) ? (int) $form->getValue('id') : 0;
// Build the query.
$query->select('COUNT(*)')
->from($db->quoteName('#__users'))
->where(
[
$db->quoteName('username') . ' = :username',
$db->quoteName('id') . ' <> :userId',
]
)
->bind(':username', $value)
->bind(':userId', $userId, ParameterType::INTEGER);
// Set and query the database.
$db->setQuery($query);
$duplicate = (bool) $db->loadResult();
if ($duplicate) {
return false;
}
return true;
}
}
URL
The URL field does not require a regular expression. Various requirements are queried successively. If a requirement is not given, false
is returned. Otherwise, the test is successful.
<?php
namespace Joomla\CMS\Form\Rule;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormRule;
use Joomla\CMS\Language\Text;
use Joomla\Registry\Registry;
use Joomla\String\StringHelper;
use Joomla\Uri\UriHelper;
class UrlRule extends FormRule
{
public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
{
// If the field is empty and not required, the field is valid.
$required = ((string) $element['required'] === 'true' || (string) $element['required'] === 'required');
if (!$required && empty($value)) {
return true;
}
$urlParts = UriHelper::parse_url($value);
// See https://www.w3.org/Addressing/URL/url-spec.txt
// Use the full list or optionally specify a list of permitted schemes.
if ($element['schemes'] == '') {
$scheme = array('http', 'https', 'ftp', 'ftps', 'gopher', 'mailto', 'news', 'prospero', 'telnet', 'rlogin', 'sftp', 'tn3270', 'wais',
'mid', 'cid', 'nntp', 'tel', 'urn', 'ldap', 'file', 'fax', 'modem', 'git');
} else {
$scheme = explode(',', $element['schemes']);
}
/*
* Note that parse_url() does not always parse accurately without a scheme,
* but at least the path should be set always. Note also that parse_url()
* returns False for seriously malformed URLs instead of an associative array.
* @link https://www.php.net/manual/en/function.parse-url.php
*/
if ($urlParts === false || !\array_key_exists('scheme', $urlParts)) {
/*
* The function parse_url() returned false (seriously malformed URL) or no scheme
* was found and the relative option is not set: in both cases the field is not valid.
*/
if ($urlParts === false || !$element['relative']) {
$element->addAttribute('message', Text::sprintf('JLIB_FORM_VALIDATE_FIELD_URL_SCHEMA_MISSING', $value, implode(', ', $scheme)));
return false;
}
// The best we can do for the rest is make sure that the path exists and is valid UTF-8.
if (!\array_key_exists('path', $urlParts) || !StringHelper::valid((string) $urlParts['path'])) {
return false;
}
// The internal URL seems to be good.
return true;
}
// Scheme found, check all parts found.
$urlScheme = (string) $urlParts['scheme'];
$urlScheme = strtolower($urlScheme);
if (\in_array($urlScheme, $scheme) == false) {
return false;
}
// For some schemes here must be two slashes.
$scheme = array('http', 'https', 'ftp', 'ftps', 'gopher', 'wais', 'prospero', 'sftp', 'telnet', 'git');
if (\in_array($urlScheme, $scheme) && substr($value, \strlen($urlScheme), 3) !== '://') {
return false;
}
// The best we can do for the rest is make sure that the strings are valid UTF-8
// and the port is an integer.
if (\array_key_exists('host', $urlParts) && !StringHelper::valid((string) $urlParts['host'])) {
return false;
}
if (\array_key_exists('port', $urlParts) && !\is_int((int) $urlParts['port'])) {
return false;
}
if (\array_key_exists('path', $urlParts) && !StringHelper::valid((string) $urlParts['path'])) {
return false;
}
return true;
}
}
Test your Joomla component
-
install your component in Joomla version 4 to test it: Copy the files in the
administrator
folder into theadministrator
folder of your Joomla 4 installation. A new installation is not necessary. Continue using the ones from the previous part. -
Open the view of your component in the administration area and create a new item or edit an existing one. Enter a number in the text field for the name.
-
Then edit another field, for example set the access to
Registered
. -
make sure that you don't get any warning at this time.
-
try to save your input at the end. This is not possible. You will see a warning.
Did you notice it? You may see the warning only after you have made a lot of changes in the form and want to save all changes. In this small extension it does not matter. In large forms, the hint at the end can be frustrating. A user may want to see it immediately after the incorrect input. So it is possible to act immediately and avoid unnecessary work. It is also frustrating to have to do all the input again. This is where client-side validation comes into play. We will look at this in the next part.
Webmentions