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
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
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: and . If * the form control is set to 'joomla' however, the fields would be rendered like: * and . * * @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
, 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('
'); } } // 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('
'); } 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 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 elements that are underneath a
element * with the appropriate name attribute, and also any 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 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 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 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; } } } } }