2095 lines
55 KiB
PHP
2095 lines
55 KiB
PHP
<?php
|
|
/**
|
|
* @package Joomla.Platform
|
|
* @subpackage Form
|
|
*
|
|
* @copyright Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
|
|
* @license GNU General Public License version 2 or later; see LICENSE
|
|
*/
|
|
|
|
defined('JPATH_PLATFORM') or die;
|
|
|
|
jimport('joomla.filesystem.path');
|
|
jimport('joomla.utilities.arrayhelper');
|
|
|
|
/**
|
|
* Form Class for the Joomla Platform.
|
|
*
|
|
* This class implements a robust API for constructing, populating, filtering, and validating forms.
|
|
* It uses XML definitions to construct form fields and a variety of field and rule classes to
|
|
* render and validate the form.
|
|
*
|
|
* @package Joomla.Platform
|
|
* @subpackage Form
|
|
* @link http://www.w3.org/TR/html4/interact/forms.html
|
|
* @link http://www.w3.org/TR/html5/forms.html
|
|
* @since 11.1
|
|
*/
|
|
class JForm
|
|
{
|
|
/**
|
|
* The JRegistry data store for form fields during display.
|
|
* @var object
|
|
* @since 11.1
|
|
*/
|
|
protected $data;
|
|
|
|
/**
|
|
* The form object errors array.
|
|
* @var array
|
|
* @since 11.1
|
|
*/
|
|
protected $errors = array();
|
|
|
|
/**
|
|
* The name of the form instance.
|
|
* @var string
|
|
* @since 11.1
|
|
*/
|
|
protected $name;
|
|
|
|
/**
|
|
* The form object options for use in rendering and validation.
|
|
* @var array
|
|
* @since 11.1
|
|
*/
|
|
protected $options = array();
|
|
|
|
/**
|
|
* The form XML definition.
|
|
* @var SimpleXMLElement
|
|
* @since 11.1
|
|
*/
|
|
protected $xml;
|
|
|
|
/**
|
|
* Form instances.
|
|
* @var array
|
|
* @since 11.1
|
|
*/
|
|
protected static $forms = array();
|
|
|
|
/**
|
|
* Method to instantiate the form object.
|
|
*
|
|
* @param string $name The name of the form.
|
|
* @param array $options An array of form options.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function __construct($name, array $options = array())
|
|
{
|
|
// Set the name for the form.
|
|
$this->name = $name;
|
|
|
|
// Initialise the JRegistry data.
|
|
$this->data = new JRegistry;
|
|
|
|
// Set the options if specified.
|
|
$this->options['control'] = isset($options['control']) ? $options['control'] : false;
|
|
}
|
|
|
|
/**
|
|
* Method to bind data to the form.
|
|
*
|
|
* @param mixed $data An array or object of data to bind to the form.
|
|
*
|
|
* @return boolean True on success.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function bind($data)
|
|
{
|
|
// Make sure there is a valid JForm XML document.
|
|
if (!($this->xml instanceof SimpleXMLElement))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// The data must be an object or array.
|
|
if (!is_object($data) && !is_array($data))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Convert the input to an array.
|
|
if (is_object($data))
|
|
{
|
|
if ($data instanceof JRegistry)
|
|
{
|
|
// Handle a JRegistry.
|
|
$data = $data->toArray();
|
|
}
|
|
elseif ($data instanceof JObject)
|
|
{
|
|
// Handle a JObject.
|
|
$data = $data->getProperties();
|
|
}
|
|
else
|
|
{
|
|
// Handle other types of objects.
|
|
$data = (array) $data;
|
|
}
|
|
}
|
|
|
|
// Process the input data.
|
|
foreach ($data as $k => $v)
|
|
{
|
|
|
|
if ($this->findField($k))
|
|
{
|
|
// If the field exists set the value.
|
|
$this->data->set($k, $v);
|
|
}
|
|
elseif (is_object($v) || JArrayHelper::isAssociative($v))
|
|
{
|
|
// If the value is an object or an associative array hand it off to the recursive bind level method.
|
|
$this->bindLevel($k, $v);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Method to bind data to the form for the group level.
|
|
*
|
|
* @param string $group The dot-separated form group path on which to bind the data.
|
|
* @param mixed $data An array or object of data to bind to the form for the group level.
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
protected function bindLevel($group, $data)
|
|
{
|
|
// Ensure the input data is an array.
|
|
settype($data, 'array');
|
|
|
|
// Process the input data.
|
|
foreach ($data as $k => $v)
|
|
{
|
|
|
|
if ($this->findField($k, $group))
|
|
{
|
|
// If the field exists set the value.
|
|
$this->data->set($group . '.' . $k, $v);
|
|
}
|
|
elseif (is_object($v) || JArrayHelper::isAssociative($v))
|
|
{
|
|
// If the value is an object or an associative array, hand it off to the recursive bind level method
|
|
$this->bindLevel($group . '.' . $k, $v);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Method to filter the form data.
|
|
*
|
|
* @param array $data An array of field values to filter.
|
|
* @param string $group The dot-separated form group path on which to filter the fields.
|
|
*
|
|
* @return mixed Array or false.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function filter($data, $group = null)
|
|
{
|
|
// Make sure there is a valid JForm XML document.
|
|
if (!($this->xml instanceof SimpleXMLElement))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
$input = new JRegistry($data);
|
|
$output = new JRegistry;
|
|
|
|
// Get the fields for which to filter the data.
|
|
$fields = $this->findFieldsByGroup($group);
|
|
if (!$fields)
|
|
{
|
|
// PANIC!
|
|
return false;
|
|
}
|
|
|
|
// Filter the fields.
|
|
foreach ($fields as $field)
|
|
{
|
|
$name = (string) $field['name'];
|
|
|
|
// Get the field groups for the element.
|
|
$attrs = $field->xpath('ancestor::fields[@name]/@name');
|
|
$groups = array_map('strval', $attrs ? $attrs : array());
|
|
$group = implode('.', $groups);
|
|
|
|
// Get the field value from the data input.
|
|
if ($group)
|
|
{
|
|
// Filter the value if it exists.
|
|
if ($input->exists($group . '.' . $name))
|
|
{
|
|
$output->set($group . '.' . $name, $this->filterField($field, $input->get($group . '.' . $name, (string) $field['default'])));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Filter the value if it exists.
|
|
if ($input->exists($name))
|
|
{
|
|
$output->set($name, $this->filterField($field, $input->get($name, (string) $field['default'])));
|
|
}
|
|
}
|
|
}
|
|
|
|
return $output->toArray();
|
|
}
|
|
|
|
/**
|
|
* Return all errors, if any.
|
|
*
|
|
* @return array Array of error messages or RuntimeException objects.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function getErrors()
|
|
{
|
|
return $this->errors;
|
|
}
|
|
|
|
/**
|
|
* Method to get a form field represented as a JFormField object.
|
|
*
|
|
* @param string $name The name of the form field.
|
|
* @param string $group The optional dot-separated form group path on which to find the field.
|
|
* @param mixed $value The optional value to use as the default for the field.
|
|
*
|
|
* @return mixed The JFormField object for the field or boolean false on error.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function getField($name, $group = null, $value = null)
|
|
{
|
|
// Make sure there is a valid JForm XML document.
|
|
if (!($this->xml instanceof SimpleXMLElement))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Attempt to find the field by name and group.
|
|
$element = $this->findField($name, $group);
|
|
|
|
// If the field element was not found return false.
|
|
if (!$element)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return $this->loadField($element, $group, $value);
|
|
}
|
|
|
|
/**
|
|
* Method to get an attribute value from a field XML element. If the attribute doesn't exist or
|
|
* is null then the optional default value will be used.
|
|
*
|
|
* @param string $name The name of the form field for which to get the attribute value.
|
|
* @param string $attribute The name of the attribute for which to get a value.
|
|
* @param mixed $default The optional default value to use if no attribute value exists.
|
|
* @param string $group The optional dot-separated form group path on which to find the field.
|
|
*
|
|
* @return mixed The attribute value for the field.
|
|
*
|
|
* @since 11.1
|
|
* @throws UnexpectedValueException
|
|
*/
|
|
public function getFieldAttribute($name, $attribute, $default = null, $group = null)
|
|
{
|
|
// Make sure there is a valid JForm XML document.
|
|
if (!($this->xml instanceof SimpleXMLElement))
|
|
{
|
|
throw new UnexpectedValueException(sprintf('%s::getFieldAttribute `xml` is not an instance of SimpleXMLElement', get_class($this)));
|
|
}
|
|
|
|
// Find the form field element from the definition.
|
|
$element = $this->findField($name, $group);
|
|
|
|
// If the element exists and the attribute exists for the field return the attribute value.
|
|
if (($element instanceof SimpleXMLElement) && ((string) $element[$attribute]))
|
|
{
|
|
return (string) $element[$attribute];
|
|
}
|
|
// Otherwise return the given default value.
|
|
else
|
|
{
|
|
return $default;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Method to get an array of JFormField objects in a given fieldset by name. If no name is
|
|
* given then all fields are returned.
|
|
*
|
|
* @param string $set The optional name of the fieldset.
|
|
*
|
|
* @return array The array of JFormField objects in the fieldset.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function getFieldset($set = null)
|
|
{
|
|
$fields = array();
|
|
|
|
// Get all of the field elements in the fieldset.
|
|
if ($set)
|
|
{
|
|
$elements = $this->findFieldsByFieldset($set);
|
|
}
|
|
// Get all fields.
|
|
else
|
|
{
|
|
$elements = $this->findFieldsByGroup();
|
|
}
|
|
|
|
// If no field elements were found return empty.
|
|
if (empty($elements))
|
|
{
|
|
return $fields;
|
|
}
|
|
|
|
// Build the result array from the found field elements.
|
|
foreach ($elements as $element)
|
|
{
|
|
// Get the field groups for the element.
|
|
$attrs = $element->xpath('ancestor::fields[@name]/@name');
|
|
$groups = array_map('strval', $attrs ? $attrs : array());
|
|
$group = implode('.', $groups);
|
|
|
|
// If the field is successfully loaded add it to the result array.
|
|
if ($field = $this->loadField($element, $group))
|
|
{
|
|
$fields[$field->id] = $field;
|
|
}
|
|
}
|
|
|
|
return $fields;
|
|
}
|
|
|
|
/**
|
|
* Method to get an array of fieldset objects optionally filtered over a given field group.
|
|
*
|
|
* @param string $group The dot-separated form group path on which to filter the fieldsets.
|
|
*
|
|
* @return array The array of fieldset objects.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function getFieldsets($group = null)
|
|
{
|
|
$fieldsets = array();
|
|
$sets = array();
|
|
|
|
// Make sure there is a valid JForm XML document.
|
|
if (!($this->xml instanceof SimpleXMLElement))
|
|
{
|
|
return $fieldsets;
|
|
}
|
|
|
|
if ($group)
|
|
{
|
|
// Get the fields elements for a given group.
|
|
$elements = &$this->findGroup($group);
|
|
|
|
foreach ($elements as &$element)
|
|
{
|
|
// Get an array of <fieldset /> elements and fieldset attributes within the fields element.
|
|
if ($tmp = $element->xpath('descendant::fieldset[@name] | descendant::field[@fieldset]/@fieldset'))
|
|
{
|
|
$sets = array_merge($sets, (array) $tmp);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Get an array of <fieldset /> elements and fieldset attributes.
|
|
$sets = $this->xml->xpath('//fieldset[@name] | //field[@fieldset]/@fieldset');
|
|
}
|
|
|
|
// If no fieldsets are found return empty.
|
|
if (empty($sets))
|
|
{
|
|
|
|
return $fieldsets;
|
|
}
|
|
|
|
// Process each found fieldset.
|
|
foreach ($sets as $set)
|
|
{
|
|
// Are we dealing with a fieldset element?
|
|
if ((string) $set['name'])
|
|
{
|
|
|
|
// Only create it if it doesn't already exist.
|
|
if (empty($fieldsets[(string) $set['name']]))
|
|
{
|
|
|
|
// Build the fieldset object.
|
|
$fieldset = (object) array('name' => '', 'label' => '', 'description' => '');
|
|
foreach ($set->attributes() as $name => $value)
|
|
{
|
|
$fieldset->$name = (string) $value;
|
|
}
|
|
|
|
// Add the fieldset object to the list.
|
|
$fieldsets[$fieldset->name] = $fieldset;
|
|
}
|
|
}
|
|
// Must be dealing with a fieldset attribute.
|
|
else
|
|
{
|
|
|
|
// Only create it if it doesn't already exist.
|
|
if (empty($fieldsets[(string) $set]))
|
|
{
|
|
|
|
// Attempt to get the fieldset element for data (throughout the entire form document).
|
|
$tmp = $this->xml->xpath('//fieldset[@name="' . (string) $set . '"]');
|
|
|
|
// If no element was found, build a very simple fieldset object.
|
|
if (empty($tmp))
|
|
{
|
|
$fieldset = (object) array('name' => (string) $set, 'label' => '', 'description' => '');
|
|
}
|
|
// Build the fieldset object from the element.
|
|
else
|
|
{
|
|
$fieldset = (object) array('name' => '', 'label' => '', 'description' => '');
|
|
foreach ($tmp[0]->attributes() as $name => $value)
|
|
{
|
|
$fieldset->$name = (string) $value;
|
|
}
|
|
}
|
|
|
|
// Add the fieldset object to the list.
|
|
$fieldsets[$fieldset->name] = $fieldset;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $fieldsets;
|
|
}
|
|
|
|
/**
|
|
* Method to get the form control. This string serves as a container for all form fields. For
|
|
* example, if there is a field named 'foo' and a field named 'bar' and the form control is
|
|
* empty the fields will be rendered like: <input name="foo" /> and <input name="bar" />. If
|
|
* the form control is set to 'joomla' however, the fields would be rendered like:
|
|
* <input name="joomla[foo]" /> and <input name="joomla[bar]" />.
|
|
*
|
|
* @return string The form control string.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function getFormControl()
|
|
{
|
|
return (string) $this->options['control'];
|
|
}
|
|
|
|
/**
|
|
* Method to get an array of JFormField objects in a given field group by name.
|
|
*
|
|
* @param string $group The dot-separated form group path for which to get the form fields.
|
|
* @param boolean $nested True to also include fields in nested groups that are inside of the
|
|
* group for which to find fields.
|
|
*
|
|
* @return array The array of JFormField objects in the field group.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function getGroup($group, $nested = false)
|
|
{
|
|
$fields = array();
|
|
|
|
// Get all of the field elements in the field group.
|
|
$elements = $this->findFieldsByGroup($group, $nested);
|
|
|
|
// If no field elements were found return empty.
|
|
if (empty($elements))
|
|
{
|
|
return $fields;
|
|
}
|
|
|
|
// Build the result array from the found field elements.
|
|
foreach ($elements as $element)
|
|
{
|
|
// Get the field groups for the element.
|
|
$attrs = $element->xpath('ancestor::fields[@name]/@name');
|
|
$groups = array_map('strval', $attrs ? $attrs : array());
|
|
$group = implode('.', $groups);
|
|
|
|
// If the field is successfully loaded add it to the result array.
|
|
if ($field = $this->loadField($element, $group))
|
|
{
|
|
$fields[$field->id] = $field;
|
|
}
|
|
}
|
|
|
|
return $fields;
|
|
}
|
|
|
|
/**
|
|
* Method to get a form field markup for the field input.
|
|
*
|
|
* @param string $name The name of the form field.
|
|
* @param string $group The optional dot-separated form group path on which to find the field.
|
|
* @param mixed $value The optional value to use as the default for the field.
|
|
*
|
|
* @return string The form field markup.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function getInput($name, $group = null, $value = null)
|
|
{
|
|
// Attempt to get the form field.
|
|
if ($field = $this->getField($name, $group, $value))
|
|
{
|
|
return $field->input;
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Method to get the label for a field input.
|
|
*
|
|
* @param string $name The name of the form field.
|
|
* @param string $group The optional dot-separated form group path on which to find the field.
|
|
*
|
|
* @return string The form field label.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function getLabel($name, $group = null)
|
|
{
|
|
// Attempt to get the form field.
|
|
if ($field = $this->getField($name, $group))
|
|
{
|
|
return $field->label;
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Method to get the form name.
|
|
*
|
|
* @return string The name of the form.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function getName()
|
|
{
|
|
return $this->name;
|
|
}
|
|
|
|
/**
|
|
* Method to get the value of a field.
|
|
*
|
|
* @param string $name The name of the field for which to get the value.
|
|
* @param string $group The optional dot-separated form group path on which to get the value.
|
|
* @param mixed $default The optional default value of the field value is empty.
|
|
*
|
|
* @return mixed The value of the field or the default value if empty.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function getValue($name, $group = null, $default = null)
|
|
{
|
|
// If a group is set use it.
|
|
if ($group)
|
|
{
|
|
$return = $this->data->get($group . '.' . $name, $default);
|
|
}
|
|
else
|
|
{
|
|
$return = $this->data->get($name, $default);
|
|
}
|
|
|
|
return $return;
|
|
}
|
|
|
|
/**
|
|
* Method to load the form description from an XML string or object.
|
|
*
|
|
* The replace option works per field. If a field being loaded already exists in the current
|
|
* form definition then the behavior or load will vary depending upon the replace flag. If it
|
|
* is set to true, then the existing field will be replaced in its exact location by the new
|
|
* field being loaded. If it is false, then the new field being loaded will be ignored and the
|
|
* method will move on to the next field to load.
|
|
*
|
|
* @param string $data The name of an XML string or object.
|
|
* @param string $replace Flag to toggle whether form fields should be replaced if a field
|
|
* already exists with the same group/name.
|
|
* @param string $xpath An optional xpath to search for the fields.
|
|
*
|
|
* @return boolean True on success, false otherwise.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function load($data, $replace = true, $xpath = false)
|
|
{
|
|
// If the data to load isn't already an XML element or string return false.
|
|
if ((!($data instanceof SimpleXMLElement)) && (!is_string($data)))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Attempt to load the XML if a string.
|
|
if (is_string($data))
|
|
{
|
|
try
|
|
{
|
|
$data = new SimpleXMLElement($data);
|
|
}
|
|
catch (Exception $e)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Make sure the XML loaded correctly.
|
|
if (!$data)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// If we have no XML definition at this point let's make sure we get one.
|
|
if (empty($this->xml))
|
|
{
|
|
// If no XPath query is set to search for fields, and we have a <form />, set it and return.
|
|
if (!$xpath && ($data->getName() == 'form'))
|
|
{
|
|
$this->xml = $data;
|
|
|
|
// Synchronize any paths found in the load.
|
|
$this->syncPaths();
|
|
|
|
return true;
|
|
}
|
|
// Create a root element for the form.
|
|
else
|
|
{
|
|
$this->xml = new SimpleXMLElement('<form></form>');
|
|
}
|
|
}
|
|
|
|
// Get the XML elements to load.
|
|
$elements = array();
|
|
if ($xpath)
|
|
{
|
|
$elements = $data->xpath($xpath);
|
|
}
|
|
elseif ($data->getName() == 'form')
|
|
{
|
|
$elements = $data->children();
|
|
}
|
|
|
|
// If there is nothing to load return true.
|
|
if (empty($elements))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Load the found form elements.
|
|
foreach ($elements as $element)
|
|
{
|
|
// Get an array of fields with the correct name.
|
|
$fields = $element->xpath('descendant-or-self::field');
|
|
foreach ($fields as $field)
|
|
{
|
|
// Get the group names as strings for ancestor fields elements.
|
|
$attrs = $field->xpath('ancestor::fields[@name]/@name');
|
|
$groups = array_map('strval', $attrs ? $attrs : array());
|
|
|
|
// Check to see if the field exists in the current form.
|
|
if ($current = $this->findField((string) $field['name'], implode('.', $groups)))
|
|
{
|
|
|
|
// If set to replace found fields, replace the data and remove the field so we don't add it twice.
|
|
if ($replace)
|
|
{
|
|
$olddom = dom_import_simplexml($current);
|
|
$loadeddom = dom_import_simplexml($field);
|
|
$addeddom = $olddom->ownerDocument->importNode($loadeddom);
|
|
$olddom->parentNode->replaceChild($addeddom, $olddom);
|
|
$loadeddom->parentNode->removeChild($loadeddom);
|
|
}
|
|
else
|
|
{
|
|
unset($field);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Merge the new field data into the existing XML document.
|
|
self::addNode($this->xml, $element);
|
|
}
|
|
|
|
// Synchronize any paths found in the load.
|
|
$this->syncPaths();
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Method to load the form description from an XML file.
|
|
*
|
|
* The reset option works on a group basis. If the XML file references
|
|
* groups that have already been created they will be replaced with the
|
|
* fields in the new XML file unless the $reset parameter has been set
|
|
* to false.
|
|
*
|
|
* @param string $file The filesystem path of an XML file.
|
|
* @param string $reset Flag to toggle whether form fields should be replaced if a field
|
|
* already exists with the same group/name.
|
|
* @param string $xpath An optional xpath to search for the fields.
|
|
*
|
|
* @return boolean True on success, false otherwise.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function loadFile($file, $reset = true, $xpath = false)
|
|
{
|
|
// Check to see if the path is an absolute path.
|
|
if (!is_file($file))
|
|
{
|
|
|
|
// Not an absolute path so let's attempt to find one using JPath.
|
|
$file = JPath::find(self::addFormPath(), strtolower($file) . '.xml');
|
|
|
|
// If unable to find the file return false.
|
|
if (!$file)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
// Attempt to load the XML file.
|
|
$xml = simplexml_load_file($file);
|
|
|
|
return $this->load($xml, $reset, $xpath);
|
|
}
|
|
|
|
/**
|
|
* Method to remove a field from the form definition.
|
|
*
|
|
* @param string $name The name of the form field for which remove.
|
|
* @param string $group The optional dot-separated form group path on which to find the field.
|
|
*
|
|
* @return boolean True on success.
|
|
*
|
|
* @since 11.1
|
|
* @throws UnexpectedValueException
|
|
*/
|
|
public function removeField($name, $group = null)
|
|
{
|
|
// Make sure there is a valid JForm XML document.
|
|
if (!($this->xml instanceof SimpleXMLElement))
|
|
{
|
|
throw new UnexpectedValueException(sprintf('%s::getFieldAttribute `xml` is not an instance of SimpleXMLElement', get_class($this)));
|
|
}
|
|
|
|
// Find the form field element from the definition.
|
|
$element = $this->findField($name, $group);
|
|
|
|
// If the element exists remove it from the form definition.
|
|
if ($element instanceof SimpleXMLElement)
|
|
{
|
|
$dom = dom_import_simplexml($element);
|
|
$dom->parentNode->removeChild($dom);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Method to remove a group from the form definition.
|
|
*
|
|
* @param string $group The dot-separated form group path for the group to remove.
|
|
*
|
|
* @return boolean True on success.
|
|
*
|
|
* @since 11.1
|
|
* @throws UnexpectedValueException
|
|
*/
|
|
public function removeGroup($group)
|
|
{
|
|
// Make sure there is a valid JForm XML document.
|
|
if (!($this->xml instanceof SimpleXMLElement))
|
|
{
|
|
throw new UnexpectedValueException(sprintf('%s::getFieldAttribute `xml` is not an instance of SimpleXMLElement', get_class($this)));
|
|
}
|
|
|
|
// Get the fields elements for a given group.
|
|
$elements = &$this->findGroup($group);
|
|
foreach ($elements as &$element)
|
|
{
|
|
$dom = dom_import_simplexml($element);
|
|
$dom->parentNode->removeChild($dom);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Method to reset the form data store and optionally the form XML definition.
|
|
*
|
|
* @param boolean $xml True to also reset the XML form definition.
|
|
*
|
|
* @return boolean True on success.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function reset($xml = false)
|
|
{
|
|
unset($this->data);
|
|
$this->data = new JRegistry;
|
|
|
|
if ($xml)
|
|
{
|
|
unset($this->xml);
|
|
$this->xml = new SimpleXMLElement('<form></form>');
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Method to set a field XML element to the form definition. If the replace flag is set then
|
|
* the field will be set whether it already exists or not. If it isn't set, then the field
|
|
* will not be replaced if it already exists.
|
|
*
|
|
* @param SimpleXMLElement $element The XML element object representation of the form field.
|
|
* @param string $group The optional dot-separated form group path on which to set the field.
|
|
* @param boolean $replace True to replace an existing field if one already exists.
|
|
*
|
|
* @return boolean True on success.
|
|
*
|
|
* @since 11.1
|
|
* @throws UnexpectedValueException
|
|
*/
|
|
public function setField(SimpleXMLElement $element, $group = null, $replace = true)
|
|
{
|
|
// Make sure there is a valid JForm XML document.
|
|
if (!($this->xml instanceof SimpleXMLElement))
|
|
{
|
|
throw new UnexpectedValueException(sprintf('%s::getFieldAttribute `xml` is not an instance of SimpleXMLElement', get_class($this)));
|
|
}
|
|
|
|
// Find the form field element from the definition.
|
|
$old = &$this->findField((string) $element['name'], $group);
|
|
|
|
// If an existing field is found and replace flag is false do nothing and return true.
|
|
if (!$replace && !empty($old))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// If an existing field is found and replace flag is true remove the old field.
|
|
if ($replace && !empty($old) && ($old instanceof SimpleXMLElement))
|
|
{
|
|
$dom = dom_import_simplexml($old);
|
|
$dom->parentNode->removeChild($dom);
|
|
}
|
|
|
|
// If no existing field is found find a group element and add the field as a child of it.
|
|
if ($group)
|
|
{
|
|
|
|
// Get the fields elements for a given group.
|
|
$fields = &$this->findGroup($group);
|
|
|
|
// If an appropriate fields element was found for the group, add the element.
|
|
if (isset($fields[0]) && ($fields[0] instanceof SimpleXMLElement))
|
|
{
|
|
self::addNode($fields[0], $element);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Set the new field to the form.
|
|
self::addNode($this->xml, $element);
|
|
}
|
|
|
|
// Synchronize any paths found in the load.
|
|
$this->syncPaths();
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Method to set an attribute value for a field XML element.
|
|
*
|
|
* @param string $name The name of the form field for which to set the attribute value.
|
|
* @param string $attribute The name of the attribute for which to set a value.
|
|
* @param mixed $value The value to set for the attribute.
|
|
* @param string $group The optional dot-separated form group path on which to find the field.
|
|
*
|
|
* @return boolean True on success.
|
|
*
|
|
* @since 11.1
|
|
* @throws UnexpectedValueException
|
|
*/
|
|
public function setFieldAttribute($name, $attribute, $value, $group = null)
|
|
{
|
|
// Make sure there is a valid JForm XML document.
|
|
if (!($this->xml instanceof SimpleXMLElement))
|
|
{
|
|
throw new UnexpectedValueException(sprintf('%s::getFieldAttribute `xml` is not an instance of SimpleXMLElement', get_class($this)));
|
|
}
|
|
|
|
// Find the form field element from the definition.
|
|
$element = $this->findField($name, $group);
|
|
|
|
// If the element doesn't exist return false.
|
|
if (!($element instanceof SimpleXMLElement))
|
|
{
|
|
return false;
|
|
}
|
|
// Otherwise set the attribute and return true.
|
|
else
|
|
{
|
|
$element[$attribute] = $value;
|
|
|
|
// Synchronize any paths found in the load.
|
|
$this->syncPaths();
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Method to set some field XML elements to the form definition. If the replace flag is set then
|
|
* the fields will be set whether they already exists or not. If it isn't set, then the fields
|
|
* will not be replaced if they already exist.
|
|
*
|
|
* @param array &$elements The array of XML element object representations of the form fields.
|
|
* @param string $group The optional dot-separated form group path on which to set the fields.
|
|
* @param boolean $replace True to replace existing fields if they already exist.
|
|
*
|
|
* @return boolean True on success.
|
|
*
|
|
* @since 11.1
|
|
* @throws UnexpectedValueException
|
|
*/
|
|
public function setFields(&$elements, $group = null, $replace = true)
|
|
{
|
|
// Make sure there is a valid JForm XML document.
|
|
if (!($this->xml instanceof SimpleXMLElement))
|
|
{
|
|
throw new UnexpectedValueException(sprintf('%s::getFieldAttribute `xml` is not an instance of SimpleXMLElement', get_class($this)));
|
|
}
|
|
|
|
// Make sure the elements to set are valid.
|
|
foreach ($elements as $element)
|
|
{
|
|
if (!($element instanceof SimpleXMLElement))
|
|
{
|
|
throw new UnexpectedValueException(sprintf('$element not SimpleXMLElement in %s::setFields', get_class($this)));
|
|
}
|
|
}
|
|
|
|
// Set the fields.
|
|
$return = true;
|
|
foreach ($elements as $element)
|
|
{
|
|
if (!$this->setField($element, $group, $replace))
|
|
{
|
|
$return = false;
|
|
}
|
|
}
|
|
|
|
// Synchronize any paths found in the load.
|
|
$this->syncPaths();
|
|
|
|
return $return;
|
|
}
|
|
|
|
/**
|
|
* Method to set the value of a field. If the field does not exist in the form then the method
|
|
* will return false.
|
|
*
|
|
* @param string $name The name of the field for which to set the value.
|
|
* @param string $group The optional dot-separated form group path on which to find the field.
|
|
* @param mixed $value The value to set for the field.
|
|
*
|
|
* @return boolean True on success.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function setValue($name, $group = null, $value = null)
|
|
{
|
|
// If the field does not exist return false.
|
|
if (!$this->findField($name, $group))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// If a group is set use it.
|
|
if ($group)
|
|
{
|
|
$this->data->set($group . '.' . $name, $value);
|
|
}
|
|
else
|
|
{
|
|
$this->data->set($name, $value);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Method to validate form data.
|
|
*
|
|
* Validation warnings will be pushed into JForm::errors and should be
|
|
* retrieved with JForm::getErrors() when validate returns boolean false.
|
|
*
|
|
* @param array $data An array of field values to validate.
|
|
* @param string $group The optional dot-separated form group path on which to filter the
|
|
* fields to be validated.
|
|
*
|
|
* @return mixed True on sucess.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function validate($data, $group = null)
|
|
{
|
|
// Make sure there is a valid JForm XML document.
|
|
if (!($this->xml instanceof SimpleXMLElement))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
$return = true;
|
|
|
|
// Create an input registry object from the data to validate.
|
|
$input = new JRegistry($data);
|
|
|
|
// Get the fields for which to validate the data.
|
|
$fields = $this->findFieldsByGroup($group);
|
|
if (!$fields)
|
|
{
|
|
// PANIC!
|
|
return false;
|
|
}
|
|
|
|
// Validate the fields.
|
|
foreach ($fields as $field)
|
|
{
|
|
$value = null;
|
|
$name = (string) $field['name'];
|
|
|
|
// Get the group names as strings for ancestor fields elements.
|
|
$attrs = $field->xpath('ancestor::fields[@name]/@name');
|
|
$groups = array_map('strval', $attrs ? $attrs : array());
|
|
$group = implode('.', $groups);
|
|
|
|
// Get the value from the input data.
|
|
if ($group)
|
|
{
|
|
$value = $input->get($group . '.' . $name);
|
|
}
|
|
else
|
|
{
|
|
$value = $input->get($name);
|
|
}
|
|
|
|
// Validate the field.
|
|
$valid = $this->validateField($field, $group, $value, $input);
|
|
|
|
// Check for an error.
|
|
if ($valid instanceof Exception)
|
|
{
|
|
array_push($this->errors, $valid);
|
|
$return = false;
|
|
}
|
|
}
|
|
|
|
return $return;
|
|
}
|
|
|
|
/**
|
|
* Method to apply an input filter to a value based on field data.
|
|
*
|
|
* @param string $element The XML element object representation of the form field.
|
|
* @param mixed $value The value to filter for the field.
|
|
*
|
|
* @return mixed The filtered value.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
protected function filterField($element, $value)
|
|
{
|
|
// Make sure there is a valid SimpleXMLElement.
|
|
if (!($element instanceof SimpleXMLElement))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Get the field filter type.
|
|
$filter = (string) $element['filter'];
|
|
|
|
// Process the input value based on the filter.
|
|
$return = null;
|
|
|
|
switch (strtoupper($filter))
|
|
{
|
|
// Access Control Rules.
|
|
case 'RULES':
|
|
$return = array();
|
|
foreach ((array) $value as $action => $ids)
|
|
{
|
|
// Build the rules array.
|
|
$return[$action] = array();
|
|
foreach ($ids as $id => $p)
|
|
{
|
|
if ($p !== '')
|
|
{
|
|
$return[$action][$id] = ($p == '1' || $p == 'true') ? true : false;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
// Do nothing, thus leaving the return value as null.
|
|
case 'UNSET':
|
|
break;
|
|
|
|
// No Filter.
|
|
case 'RAW':
|
|
$return = $value;
|
|
break;
|
|
|
|
// Filter the input as an array of integers.
|
|
case 'INT_ARRAY':
|
|
// Make sure the input is an array.
|
|
if (is_object($value))
|
|
{
|
|
$value = get_object_vars($value);
|
|
}
|
|
$value = is_array($value) ? $value : array($value);
|
|
|
|
JArrayHelper::toInteger($value);
|
|
$return = $value;
|
|
break;
|
|
|
|
// Filter safe HTML.
|
|
case 'SAFEHTML':
|
|
$return = JFilterInput::getInstance(null, null, 1, 1)->clean($value, 'string');
|
|
break;
|
|
|
|
// Convert a date to UTC based on the server timezone offset.
|
|
case 'SERVER_UTC':
|
|
if ((int) $value > 0)
|
|
{
|
|
// Get the server timezone setting.
|
|
$offset = JFactory::getConfig()->get('offset');
|
|
|
|
// Return an SQL formatted datetime string in UTC.
|
|
$return = JFactory::getDate($value, $offset)->toSql();
|
|
}
|
|
else
|
|
{
|
|
$return = '';
|
|
}
|
|
break;
|
|
|
|
// Convert a date to UTC based on the user timezone offset.
|
|
case 'USER_UTC':
|
|
if ((int) $value > 0)
|
|
{
|
|
// Get the user timezone setting defaulting to the server timezone setting.
|
|
$offset = JFactory::getUser()->getParam('timezone', JFactory::getConfig()->get('offset'));
|
|
|
|
// Return a MySQL formatted datetime string in UTC.
|
|
$return = JFactory::getDate($value, $offset)->toSql();
|
|
}
|
|
else
|
|
{
|
|
$return = '';
|
|
}
|
|
break;
|
|
|
|
// Ensures a protocol is present in the saved field. Only use when
|
|
// the only permitted protocols requre '://'. See JFormRuleUrl for list of these.
|
|
|
|
case 'URL':
|
|
if (empty($value))
|
|
{
|
|
return false;
|
|
}
|
|
$value = JFilterInput::getInstance()->clean($value, 'html');
|
|
$value = trim($value);
|
|
|
|
// Check for a protocol
|
|
$protocol = parse_url($value, PHP_URL_SCHEME);
|
|
|
|
// If there is no protocol and the relative option is not specified,
|
|
// we assume that it is an external URL and prepend http://.
|
|
if (($element['type'] == 'url' && !$protocol && !$element['relative'])
|
|
|| (!$element['type'] == 'url' && !$protocol))
|
|
{
|
|
$protocol = 'http';
|
|
|
|
// If it looks like an internal link, then add the root.
|
|
if (substr($value, 0) == 'index.php')
|
|
{
|
|
$value = JUri::root() . $value;
|
|
}
|
|
|
|
// Otherwise we treat it is an external link.
|
|
// Put the url back together.
|
|
$value = $protocol . '://' . $value;
|
|
}
|
|
|
|
// If relative URLS are allowed we assume that URLs without protocols are internal.
|
|
elseif (!$protocol && $element['relative'])
|
|
{
|
|
$host = JUri::getInstance('SERVER')->gethost();
|
|
|
|
// If it starts with the host string, just prepend the protocol.
|
|
if (substr($value, 0) == $host)
|
|
{
|
|
$value = 'http://' . $value;
|
|
}
|
|
// Otherwise prepend the root.
|
|
else
|
|
{
|
|
$value = JUri::root() . $value;
|
|
}
|
|
}
|
|
|
|
$return = $value;
|
|
break;
|
|
|
|
case 'TEL':
|
|
$value = trim($value);
|
|
|
|
// Does it match the NANP pattern?
|
|
if (preg_match('/^(?:\+?1[-. ]?)?\(?([2-9][0-8][0-9])\)?[-. ]?([2-9][0-9]{2})[-. ]?([0-9]{4})$/', $value) == 1)
|
|
{
|
|
$number = (string) preg_replace('/[^\d]/', '', $value);
|
|
if (substr($number, 0, 1) == 1)
|
|
{
|
|
$number = substr($number, 1);
|
|
}
|
|
if (substr($number, 0, 2) == '+1')
|
|
{
|
|
$number = substr($number, 2);
|
|
}
|
|
$result = '1.' . $number;
|
|
}
|
|
// If not, does it match ITU-T?
|
|
elseif (preg_match('/^\+(?:[0-9] ?){6,14}[0-9]$/', $value) == 1)
|
|
{
|
|
$countrycode = substr($value, 0, strpos($value, ' '));
|
|
$countrycode = (string) preg_replace('/[^\d]/', '', $countrycode);
|
|
$number = strstr($value, ' ');
|
|
$number = (string) preg_replace('/[^\d]/', '', $number);
|
|
$result = $countrycode . '.' . $number;
|
|
}
|
|
// If not, does it match EPP?
|
|
elseif (preg_match('/^\+[0-9]{1,3}\.[0-9]{4,14}(?:x.+)?$/', $value) == 1)
|
|
{
|
|
if (strstr($value, 'x'))
|
|
{
|
|
$xpos = strpos($value, 'x');
|
|
$value = substr($value, 0, $xpos);
|
|
}
|
|
$result = str_replace('+', '', $value);
|
|
|
|
}
|
|
// Maybe it is already ccc.nnnnnnn?
|
|
elseif (preg_match('/[0-9]{1,3}\.[0-9]{4,14}$/', $value) == 1)
|
|
{
|
|
$result = $value;
|
|
}
|
|
// If not, can we make it a string of digits?
|
|
else
|
|
{
|
|
$value = (string) preg_replace('/[^\d]/', '', $value);
|
|
if ($value != null && strlen($value) <= 15)
|
|
{
|
|
$length = strlen($value);
|
|
|
|
// If it is fewer than 13 digits assume it is a local number
|
|
if ($length <= 12)
|
|
{
|
|
$result = '.' . $value;
|
|
|
|
}
|
|
else
|
|
{
|
|
// If it has 13 or more digits let's make a country code.
|
|
$cclen = $length - 12;
|
|
$result = substr($value, 0, $cclen) . '.' . substr($value, $cclen);
|
|
}
|
|
}
|
|
// If not let's not save anything.
|
|
else
|
|
{
|
|
$result = '';
|
|
}
|
|
}
|
|
$return = $result;
|
|
|
|
break;
|
|
default:
|
|
// Check for a callback filter.
|
|
if (strpos($filter, '::') !== false && is_callable(explode('::', $filter)))
|
|
{
|
|
$return = call_user_func(explode('::', $filter), $value);
|
|
}
|
|
// Filter using a callback function if specified.
|
|
elseif (function_exists($filter))
|
|
{
|
|
$return = call_user_func($filter, $value);
|
|
}
|
|
// Filter using JFilterInput. All HTML code is filtered by default.
|
|
else
|
|
{
|
|
$return = JFilterInput::getInstance()->clean($value, $filter);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return $return;
|
|
}
|
|
|
|
/**
|
|
* Method to get a form field represented as an XML element object.
|
|
*
|
|
* @param string $name The name of the form field.
|
|
* @param string $group The optional dot-separated form group path on which to find the field.
|
|
*
|
|
* @return mixed The XML element object for the field or boolean false on error.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
protected function findField($name, $group = null)
|
|
{
|
|
$element = false;
|
|
$fields = array();
|
|
|
|
// Make sure there is a valid JForm XML document.
|
|
if (!($this->xml instanceof SimpleXMLElement))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Let's get the appropriate field element based on the method arguments.
|
|
if ($group)
|
|
{
|
|
|
|
// Get the fields elements for a given group.
|
|
$elements = &$this->findGroup($group);
|
|
|
|
// Get all of the field elements with the correct name for the fields elements.
|
|
foreach ($elements as $element)
|
|
{
|
|
// If there are matching field elements add them to the fields array.
|
|
if ($tmp = $element->xpath('descendant::field[@name="' . $name . '"]'))
|
|
{
|
|
$fields = array_merge($fields, $tmp);
|
|
}
|
|
}
|
|
|
|
// Make sure something was found.
|
|
if (!$fields)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Use the first correct match in the given group.
|
|
$groupNames = explode('.', $group);
|
|
foreach ($fields as &$field)
|
|
{
|
|
// Get the group names as strings for ancestor fields elements.
|
|
$attrs = $field->xpath('ancestor::fields[@name]/@name');
|
|
$names = array_map('strval', $attrs ? $attrs : array());
|
|
|
|
// If the field is in the exact group use it and break out of the loop.
|
|
if ($names == (array) $groupNames)
|
|
{
|
|
$element = &$field;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Get an array of fields with the correct name.
|
|
$fields = $this->xml->xpath('//field[@name="' . $name . '"]');
|
|
|
|
// Make sure something was found.
|
|
if (!$fields)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Search through the fields for the right one.
|
|
foreach ($fields as &$field)
|
|
{
|
|
// If we find an ancestor fields element with a group name then it isn't what we want.
|
|
if ($field->xpath('ancestor::fields[@name]'))
|
|
{
|
|
continue;
|
|
}
|
|
// Found it!
|
|
else
|
|
{
|
|
$element = &$field;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $element;
|
|
}
|
|
|
|
/**
|
|
* Method to get an array of <field /> elements from the form XML document which are
|
|
* in a specified fieldset by name.
|
|
*
|
|
* @param string $name The name of the fieldset.
|
|
*
|
|
* @return mixed Boolean false on error or array of SimpleXMLElement objects.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
protected function &findFieldsByFieldset($name)
|
|
{
|
|
$false = false;
|
|
|
|
// Make sure there is a valid JForm XML document.
|
|
if (!($this->xml instanceof SimpleXMLElement))
|
|
{
|
|
return $false;
|
|
}
|
|
|
|
/*
|
|
* Get an array of <field /> elements that are underneath a <fieldset /> element
|
|
* with the appropriate name attribute, and also any <field /> elements with
|
|
* the appropriate fieldset attribute.
|
|
*/
|
|
$fields = $this->xml->xpath('//fieldset[@name="' . $name . '"]//field | //field[@fieldset="' . $name . '"]');
|
|
|
|
return $fields;
|
|
}
|
|
|
|
/**
|
|
* Method to get an array of <field /> elements from the form XML document which are
|
|
* in a control group by name.
|
|
*
|
|
* @param mixed $group The optional dot-separated form group path on which to find the fields.
|
|
* Null will return all fields. False will return fields not in a group.
|
|
* @param boolean $nested True to also include fields in nested groups that are inside of the
|
|
* group for which to find fields.
|
|
*
|
|
* @return mixed Boolean false on error or array of SimpleXMLElement objects.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
protected function &findFieldsByGroup($group = null, $nested = false)
|
|
{
|
|
$false = false;
|
|
$fields = array();
|
|
|
|
// Make sure there is a valid JForm XML document.
|
|
if (!($this->xml instanceof SimpleXMLElement))
|
|
{
|
|
return $false;
|
|
}
|
|
|
|
// Get only fields in a specific group?
|
|
if ($group)
|
|
{
|
|
|
|
// Get the fields elements for a given group.
|
|
$elements = &$this->findGroup($group);
|
|
|
|
// Get all of the field elements for the fields elements.
|
|
foreach ($elements as $element)
|
|
{
|
|
|
|
// If there are field elements add them to the return result.
|
|
if ($tmp = $element->xpath('descendant::field'))
|
|
{
|
|
|
|
// If we also want fields in nested groups then just merge the arrays.
|
|
if ($nested)
|
|
{
|
|
$fields = array_merge($fields, $tmp);
|
|
}
|
|
// If we want to exclude nested groups then we need to check each field.
|
|
else
|
|
{
|
|
$groupNames = explode('.', $group);
|
|
foreach ($tmp as $field)
|
|
{
|
|
// Get the names of the groups that the field is in.
|
|
$attrs = $field->xpath('ancestor::fields[@name]/@name');
|
|
$names = array_map('strval', $attrs ? $attrs : array());
|
|
|
|
// If the field is in the specific group then add it to the return list.
|
|
if ($names == (array) $groupNames)
|
|
{
|
|
$fields = array_merge($fields, array($field));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
elseif ($group === false)
|
|
{
|
|
// Get only field elements not in a group.
|
|
$fields = $this->xml->xpath('descendant::fields[not(@name)]/field | descendant::fields[not(@name)]/fieldset/field ');
|
|
}
|
|
else
|
|
{
|
|
// Get an array of all the <field /> elements.
|
|
$fields = $this->xml->xpath('//field');
|
|
}
|
|
|
|
return $fields;
|
|
}
|
|
|
|
/**
|
|
* Method to get a form field group represented as an XML element object.
|
|
*
|
|
* @param string $group The dot-separated form group path on which to find the group.
|
|
*
|
|
* @return mixed An array of XML element objects for the group or boolean false on error.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
protected function &findGroup($group)
|
|
{
|
|
$false = false;
|
|
$groups = array();
|
|
$tmp = array();
|
|
|
|
// Make sure there is a valid JForm XML document.
|
|
if (!($this->xml instanceof SimpleXMLElement))
|
|
{
|
|
return $false;
|
|
}
|
|
|
|
// Make sure there is actually a group to find.
|
|
$group = explode('.', $group);
|
|
if (!empty($group))
|
|
{
|
|
|
|
// Get any fields elements with the correct group name.
|
|
$elements = $this->xml->xpath('//fields[@name="' . (string) $group[0] . '"]');
|
|
|
|
// Check to make sure that there are no parent groups for each element.
|
|
foreach ($elements as $element)
|
|
{
|
|
if (!$element->xpath('ancestor::fields[@name]'))
|
|
{
|
|
$tmp[] = $element;
|
|
}
|
|
}
|
|
|
|
// Iterate through the nested groups to find any matching form field groups.
|
|
for ($i = 1, $n = count($group); $i < $n; $i++)
|
|
{
|
|
// Initialise some loop variables.
|
|
$validNames = array_slice($group, 0, $i + 1);
|
|
$current = $tmp;
|
|
$tmp = array();
|
|
|
|
// Check to make sure that there are no parent groups for each element.
|
|
foreach ($current as $element)
|
|
{
|
|
// Get any fields elements with the correct group name.
|
|
$children = $element->xpath('descendant::fields[@name="' . (string) $group[$i] . '"]');
|
|
|
|
// For the found fields elements validate that they are in the correct groups.
|
|
foreach ($children as $fields)
|
|
{
|
|
// Get the group names as strings for ancestor fields elements.
|
|
$attrs = $fields->xpath('ancestor-or-self::fields[@name]/@name');
|
|
$names = array_map('strval', $attrs ? $attrs : array());
|
|
|
|
// If the group names for the fields element match the valid names at this
|
|
// level add the fields element.
|
|
if ($validNames == $names)
|
|
{
|
|
$tmp[] = $fields;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Only include valid XML objects.
|
|
foreach ($tmp as $element)
|
|
{
|
|
if ($element instanceof SimpleXMLElement)
|
|
{
|
|
$groups[] = $element;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $groups;
|
|
}
|
|
|
|
/**
|
|
* Method to load, setup and return a JFormField object based on field data.
|
|
*
|
|
* @param string $element The XML element object representation of the form field.
|
|
* @param string $group The optional dot-separated form group path on which to find the field.
|
|
* @param mixed $value The optional value to use as the default for the field.
|
|
*
|
|
* @return mixed The JFormField object for the field or boolean false on error.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
protected function loadField($element, $group = null, $value = null)
|
|
{
|
|
// Make sure there is a valid SimpleXMLElement.
|
|
if (!($element instanceof SimpleXMLElement))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Get the field type.
|
|
$type = $element['type'] ? (string) $element['type'] : 'text';
|
|
|
|
// Load the JFormField object for the field.
|
|
$field = $this->loadFieldType($type);
|
|
|
|
// If the object could not be loaded, get a text field object.
|
|
if ($field === false)
|
|
{
|
|
$field = $this->loadFieldType('text');
|
|
}
|
|
|
|
/*
|
|
* Get the value for the form field if not set.
|
|
* Default to the translated version of the 'default' attribute
|
|
* if 'translate_default' attribute if set to 'true' or '1'
|
|
* else the value of the 'default' attribute for the field.
|
|
*/
|
|
if ($value === null)
|
|
{
|
|
$default = (string) $element['default'];
|
|
if (($translate = $element['translate_default']) && ((string) $translate == 'true' || (string) $translate == '1'))
|
|
{
|
|
$lang = JFactory::getLanguage();
|
|
if ($lang->hasKey($default))
|
|
{
|
|
$debug = $lang->setDebug(false);
|
|
$default = JText::_($default);
|
|
$lang->setDebug($debug);
|
|
}
|
|
else
|
|
{
|
|
$default = JText::_($default);
|
|
}
|
|
}
|
|
$value = $this->getValue((string) $element['name'], $group, $default);
|
|
}
|
|
|
|
// Setup the JFormField object.
|
|
$field->setForm($this);
|
|
|
|
if ($field->setup($element, $value, $group))
|
|
{
|
|
return $field;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Proxy for {@link JFormHelper::loadFieldType()}.
|
|
*
|
|
* @param string $type The field type.
|
|
* @param boolean $new Flag to toggle whether we should get a new instance of the object.
|
|
*
|
|
* @return mixed JFormField object on success, false otherwise.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
protected function loadFieldType($type, $new = true)
|
|
{
|
|
return JFormHelper::loadFieldType($type, $new);
|
|
}
|
|
|
|
/**
|
|
* Proxy for JFormHelper::loadRuleType().
|
|
*
|
|
* @param string $type The rule type.
|
|
* @param boolean $new Flag to toggle whether we should get a new instance of the object.
|
|
*
|
|
* @return mixed JFormRule object on success, false otherwise.
|
|
*
|
|
* @see JFormHelper::loadRuleType()
|
|
* @since 11.1
|
|
*/
|
|
protected function loadRuleType($type, $new = true)
|
|
{
|
|
return JFormHelper::loadRuleType($type, $new);
|
|
}
|
|
|
|
/**
|
|
* Method to synchronize any field, form or rule paths contained in the XML document.
|
|
*
|
|
* @return boolean True on success.
|
|
*
|
|
* @since 11.1
|
|
* @todo Maybe we should receive all addXXXpaths attributes at once?
|
|
*/
|
|
protected function syncPaths()
|
|
{
|
|
// Make sure there is a valid JForm XML document.
|
|
if (!($this->xml instanceof SimpleXMLElement))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Get any addfieldpath attributes from the form definition.
|
|
$paths = $this->xml->xpath('//*[@addfieldpath]/@addfieldpath');
|
|
$paths = array_map('strval', $paths ? $paths : array());
|
|
|
|
// Add the field paths.
|
|
foreach ($paths as $path)
|
|
{
|
|
$path = JPATH_ROOT . '/' . ltrim($path, '/\\');
|
|
self::addFieldPath($path);
|
|
}
|
|
|
|
// Get any addformpath attributes from the form definition.
|
|
$paths = $this->xml->xpath('//*[@addformpath]/@addformpath');
|
|
$paths = array_map('strval', $paths ? $paths : array());
|
|
|
|
// Add the form paths.
|
|
foreach ($paths as $path)
|
|
{
|
|
$path = JPATH_ROOT . '/' . ltrim($path, '/\\');
|
|
self::addFormPath($path);
|
|
}
|
|
|
|
// Get any addrulepath attributes from the form definition.
|
|
$paths = $this->xml->xpath('//*[@addrulepath]/@addrulepath');
|
|
$paths = array_map('strval', $paths ? $paths : array());
|
|
|
|
// Add the rule paths.
|
|
foreach ($paths as $path)
|
|
{
|
|
$path = JPATH_ROOT . '/' . ltrim($path, '/\\');
|
|
self::addRulePath($path);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Method to validate a JFormField object based on field data.
|
|
*
|
|
* @param SimpleXMLElement $element The XML element object representation of the form field.
|
|
* @param string $group The optional dot-separated form group path on which to find the field.
|
|
* @param mixed $value The optional value to use as the default for the field.
|
|
* @param JRegistry $input An optional JRegistry object with the entire data set to validate
|
|
* against the entire form.
|
|
*
|
|
* @return mixed Boolean true if field value is valid, Exception on failure.
|
|
*
|
|
* @since 11.1
|
|
* @throws InvalidArgumentException
|
|
* @throws UnexpectedValueException
|
|
*/
|
|
protected function validateField(SimpleXMLElement $element, $group = null, $value = null, JRegistry $input = null)
|
|
{
|
|
$valid = true;
|
|
|
|
// Check if the field is required.
|
|
$required = ((string) $element['required'] == 'true' || (string) $element['required'] == 'required');
|
|
|
|
if ($required)
|
|
{
|
|
// If the field is required and the value is empty return an error message.
|
|
if (($value === '') || ($value === null))
|
|
{
|
|
if ($element['label'])
|
|
{
|
|
$message = JText::_($element['label']);
|
|
}
|
|
else
|
|
{
|
|
$message = JText::_($element['name']);
|
|
}
|
|
$message = JText::sprintf('JLIB_FORM_VALIDATE_FIELD_REQUIRED', $message);
|
|
|
|
return new RuntimeException($message);
|
|
}
|
|
}
|
|
|
|
// Get the field validation rule.
|
|
if ($type = (string) $element['validate'])
|
|
{
|
|
// Load the JFormRule object for the field.
|
|
$rule = $this->loadRuleType($type);
|
|
|
|
// If the object could not be loaded return an error message.
|
|
if ($rule === false)
|
|
{
|
|
throw new UnexpectedValueException(sprintf('%s::validateField() rule `%s` missing.', get_class($this), $type));
|
|
}
|
|
|
|
// Run the field validation rule test.
|
|
$valid = $rule->test($element, $value, $group, $input, $this);
|
|
|
|
// Check for an error in the validation test.
|
|
if ($valid instanceof Exception)
|
|
{
|
|
return $valid;
|
|
}
|
|
}
|
|
|
|
// Check if the field is valid.
|
|
if ($valid === false)
|
|
{
|
|
// Does the field have a defined error message?
|
|
$message = (string) $element['message'];
|
|
|
|
if ($message)
|
|
{
|
|
$message = JText::_($element['message']);
|
|
return new UnexpectedValueException($message);
|
|
}
|
|
else
|
|
{
|
|
$message = JText::_($element['label']);
|
|
$message = JText::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', $message);
|
|
return new UnexpectedValueException($message);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Proxy for {@link JFormHelper::addFieldPath()}.
|
|
*
|
|
* @param mixed $new A path or array of paths to add.
|
|
*
|
|
* @return array The list of paths that have been added.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public static function addFieldPath($new = null)
|
|
{
|
|
return JFormHelper::addFieldPath($new);
|
|
}
|
|
|
|
/**
|
|
* Proxy for JFormHelper::addFormPath().
|
|
*
|
|
* @param mixed $new A path or array of paths to add.
|
|
*
|
|
* @return array The list of paths that have been added.
|
|
*
|
|
* @see JFormHelper::addFormPath()
|
|
* @since 11.1
|
|
*/
|
|
public static function addFormPath($new = null)
|
|
{
|
|
return JFormHelper::addFormPath($new);
|
|
}
|
|
|
|
/**
|
|
* Proxy for JFormHelper::addRulePath().
|
|
*
|
|
* @param mixed $new A path or array of paths to add.
|
|
*
|
|
* @return array The list of paths that have been added.
|
|
*
|
|
* @see JFormHelper::addRulePath()
|
|
* @since 11.1
|
|
*/
|
|
public static function addRulePath($new = null)
|
|
{
|
|
return JFormHelper::addRulePath($new);
|
|
}
|
|
|
|
/**
|
|
* Method to get an instance of a form.
|
|
*
|
|
* @param string $name The name of the form.
|
|
* @param string $data The name of an XML file or string to load as the form definition.
|
|
* @param array $options An array of form options.
|
|
* @param string $replace Flag to toggle whether form fields should be replaced if a field
|
|
* already exists with the same group/name.
|
|
* @param string $xpath An optional xpath to search for the fields.
|
|
*
|
|
* @return object JForm instance.
|
|
*
|
|
* @since 11.1
|
|
* @throws InvalidArgumentException if no data provided.
|
|
* @throws RuntimeException if the form could not be loaded.
|
|
*/
|
|
public static function getInstance($name, $data = null, $options = array(), $replace = true, $xpath = false)
|
|
{
|
|
// Reference to array with form instances
|
|
$forms = &self::$forms;
|
|
|
|
// Only instantiate the form if it does not already exist.
|
|
if (!isset($forms[$name]))
|
|
{
|
|
$data = trim($data);
|
|
|
|
if (empty($data))
|
|
{
|
|
throw new InvalidArgumentException(sprintf('JForm::getInstance(name, *%s*)', gettype($data)));
|
|
}
|
|
|
|
// Instantiate the form.
|
|
$forms[$name] = new JForm($name, $options);
|
|
|
|
// Load the data.
|
|
if (substr(trim($data), 0, 1) == '<')
|
|
{
|
|
if ($forms[$name]->load($data, $replace, $xpath) == false)
|
|
{
|
|
throw new RuntimeException('JForm::getInstance could not load form');
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ($forms[$name]->loadFile($data, $replace, $xpath) == false)
|
|
{
|
|
throw new RuntimeException('JForm::getInstance could not load file');
|
|
}
|
|
}
|
|
}
|
|
|
|
return $forms[$name];
|
|
}
|
|
|
|
/**
|
|
* Adds a new child SimpleXMLElement node to the source.
|
|
*
|
|
* @param SimpleXMLElement $source The source element on which to append.
|
|
* @param SimpleXMLElement $new The new element to append.
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
protected static function addNode(SimpleXMLElement $source, SimpleXMLElement $new)
|
|
{
|
|
// Add the new child node.
|
|
$node = $source->addChild($new->getName(), trim($new));
|
|
|
|
// Add the attributes of the child node.
|
|
foreach ($new->attributes() as $name => $value)
|
|
{
|
|
$node->addAttribute($name, $value);
|
|
}
|
|
|
|
// Add any children of the new node.
|
|
foreach ($new->children() as $child)
|
|
{
|
|
self::addNode($node, $child);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update the attributes of a child node
|
|
*
|
|
* @param SimpleXMLElement $source The source element on which to append the attributes
|
|
* @param SimpleXMLElement $new The new element to append
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
protected static function mergeNode(SimpleXMLElement $source, SimpleXMLElement $new)
|
|
{
|
|
// Update the attributes of the child node.
|
|
foreach ($new->attributes() as $name => $value)
|
|
{
|
|
if (isset($source[$name]))
|
|
{
|
|
$source[$name] = (string) $value;
|
|
}
|
|
else
|
|
{
|
|
$source->addAttribute($name, $value);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Merges new elements into a source <fields> element.
|
|
*
|
|
* @param SimpleXMLElement $source The source element.
|
|
* @param SimpleXMLElement $new The new element to merge.
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
protected static function mergeNodes(SimpleXMLElement $source, SimpleXMLElement $new)
|
|
{
|
|
// The assumption is that the inputs are at the same relative level.
|
|
// So we just have to scan the children and deal with them.
|
|
|
|
// Update the attributes of the child node.
|
|
foreach ($new->attributes() as $name => $value)
|
|
{
|
|
if (isset($source[$name]))
|
|
{
|
|
$source[$name] = (string) $value;
|
|
}
|
|
else
|
|
{
|
|
$source->addAttribute($name, $value);
|
|
}
|
|
}
|
|
|
|
foreach ($new->children() as $child)
|
|
{
|
|
$type = $child->getName();
|
|
$name = $child['name'];
|
|
|
|
// Does this node exist?
|
|
$fields = $source->xpath($type . '[@name="' . $name . '"]');
|
|
|
|
if (empty($fields))
|
|
{
|
|
// This node does not exist, so add it.
|
|
self::addNode($source, $child);
|
|
}
|
|
else
|
|
{
|
|
// This node does exist.
|
|
switch ($type)
|
|
{
|
|
case 'field':
|
|
self::mergeNode($fields[0], $child);
|
|
break;
|
|
|
|
default:
|
|
self::mergeNodes($fields[0], $child);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|