Joomla Platform  13.1
Documentation des API du framework Joomla Platform
 Tout Classes Espaces de nommage Fichiers Fonctions Variables Pages
form.php
Aller à la documentation de ce fichier.
1 <?php
2 /**
3  * @package Joomla.Platform
4  * @subpackage Form
5  *
6  * @copyright Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
7  * @license GNU General Public License version 2 or later; see LICENSE
8  */
9 
10 defined('JPATH_PLATFORM') or die;
11 
12 jimport('joomla.filesystem.path');
13 jimport('joomla.utilities.arrayhelper');
14 
15 /**
16  * Form Class for the Joomla Platform.
17  *
18  * This class implements a robust API for constructing, populating, filtering, and validating forms.
19  * It uses XML definitions to construct form fields and a variety of field and rule classes to
20  * render and validate the form.
21  *
22  * @package Joomla.Platform
23  * @subpackage Form
24  * @link http://www.w3.org/TR/html4/interact/forms.html
25  * @link http://www.w3.org/TR/html5/forms.html
26  * @since 11.1
27  */
28 class JForm
29 {
30  /**
31  * The JRegistry data store for form fields during display.
32  * @var object
33  * @since 11.1
34  */
35  protected $data;
36 
37  /**
38  * The form object errors array.
39  * @var array
40  * @since 11.1
41  */
42  protected $errors = array();
43 
44  /**
45  * The name of the form instance.
46  * @var string
47  * @since 11.1
48  */
49  protected $name;
50 
51  /**
52  * The form object options for use in rendering and validation.
53  * @var array
54  * @since 11.1
55  */
56  protected $options = array();
57 
58  /**
59  * The form XML definition.
60  * @var SimpleXMLElement
61  * @since 11.1
62  */
63  protected $xml;
64 
65  /**
66  * Form instances.
67  * @var array
68  * @since 11.1
69  */
70  protected static $forms = array();
71 
72  /**
73  * Alows extensions to implement repeating elements
74  * @var mixed
75  * @since 3.2
76  */
77  public $repeat = false;
78 
79  /**
80  * Method to instantiate the form object.
81  *
82  * @param string $name The name of the form.
83  * @param array $options An array of form options.
84  *
85  * @since 11.1
86  */
87  public function __construct($name, array $options = array())
88  {
89  // Set the name for the form.
90  $this->name = $name;
91 
92  // Initialise the JRegistry data.
93  $this->data = new JRegistry;
94 
95  // Set the options if specified.
96  $this->options['control'] = isset($options['control']) ? $options['control'] : false;
97  }
98 
99  /**
100  * Method to bind data to the form.
101  *
102  * @param mixed $data An array or object of data to bind to the form.
103  *
104  * @return boolean True on success.
105  *
106  * @since 11.1
107  */
108  public function bind($data)
109  {
110  // Make sure there is a valid JForm XML document.
111  if (!($this->xml instanceof SimpleXMLElement))
112  {
113  return false;
114  }
115 
116  // The data must be an object or array.
117  if (!is_object($data) && !is_array($data))
118  {
119  return false;
120  }
121 
122  // Convert the input to an array.
123  if (is_object($data))
124  {
125  if ($data instanceof JRegistry)
126  {
127  // Handle a JRegistry.
128  $data = $data->toArray();
129  }
130  elseif ($data instanceof JObject)
131  {
132  // Handle a JObject.
133  $data = $data->getProperties();
134  }
135  else
136  {
137  // Handle other types of objects.
138  $data = (array) $data;
139  }
140  }
141 
142  // Process the input data.
143  foreach ($data as $k => $v)
144  {
145  if ($this->findField($k))
146  {
147  // If the field exists set the value.
148  $this->data->set($k, $v);
149  }
150  elseif (is_object($v) || JArrayHelper::isAssociative($v))
151  {
152  // If the value is an object or an associative array hand it off to the recursive bind level method.
153  $this->bindLevel($k, $v);
154  }
155  }
156 
157  return true;
158  }
159 
160  /**
161  * Method to bind data to the form for the group level.
162  *
163  * @param string $group The dot-separated form group path on which to bind the data.
164  * @param mixed $data An array or object of data to bind to the form for the group level.
165  *
166  * @return void
167  *
168  * @since 11.1
169  */
170  protected function bindLevel($group, $data)
171  {
172  // Ensure the input data is an array.
173  settype($data, 'array');
174 
175  // Process the input data.
176  foreach ($data as $k => $v)
177  {
178  if ($this->findField($k, $group))
179  {
180  // If the field exists set the value.
181  $this->data->set($group . '.' . $k, $v);
182  }
183  elseif (is_object($v) || JArrayHelper::isAssociative($v))
184  {
185  // If the value is an object or an associative array, hand it off to the recursive bind level method
186  $this->bindLevel($group . '.' . $k, $v);
187  }
188  }
189  }
190 
191  /**
192  * Method to filter the form data.
193  *
194  * @param array $data An array of field values to filter.
195  * @param string $group The dot-separated form group path on which to filter the fields.
196  *
197  * @return mixed Array or false.
198  *
199  * @since 11.1
200  */
201  public function filter($data, $group = null)
202  {
203  // Make sure there is a valid JForm XML document.
204  if (!($this->xml instanceof SimpleXMLElement))
205  {
206  return false;
207  }
208 
209  $input = new JRegistry($data);
210  $output = new JRegistry;
211 
212  // Get the fields for which to filter the data.
213  $fields = $this->findFieldsByGroup($group);
214 
215  if (!$fields)
216  {
217  // PANIC!
218  return false;
219  }
220 
221  // Filter the fields.
222  foreach ($fields as $field)
223  {
224  $name = (string) $field['name'];
225 
226  // Get the field groups for the element.
227  $attrs = $field->xpath('ancestor::fields[@name]/@name');
228  $groups = array_map('strval', $attrs ? $attrs : array());
229  $group = implode('.', $groups);
230 
231  // Get the field value from the data input.
232  if ($group)
233  {
234  // Filter the value if it exists.
235  if ($input->exists($group . '.' . $name))
236  {
237  $output->set($group . '.' . $name, $this->filterField($field, $input->get($group . '.' . $name, (string) $field['default'])));
238  }
239  }
240  else
241  {
242  // Filter the value if it exists.
243  if ($input->exists($name))
244  {
245  $output->set($name, $this->filterField($field, $input->get($name, (string) $field['default'])));
246  }
247  }
248  }
249 
250  return $output->toArray();
251  }
252 
253  /**
254  * Return all errors, if any.
255  *
256  * @return array Array of error messages or RuntimeException objects.
257  *
258  * @since 11.1
259  */
260  public function getErrors()
261  {
262  return $this->errors;
263  }
264 
265  /**
266  * Method to get a form field represented as a JFormField object.
267  *
268  * @param string $name The name of the form field.
269  * @param string $group The optional dot-separated form group path on which to find the field.
270  * @param mixed $value The optional value to use as the default for the field.
271  *
272  * @return mixed The JFormField object for the field or boolean false on error.
273  *
274  * @since 11.1
275  */
276  public function getField($name, $group = null, $value = null)
277  {
278  // Make sure there is a valid JForm XML document.
279  if (!($this->xml instanceof SimpleXMLElement))
280  {
281  return false;
282  }
283 
284  // Attempt to find the field by name and group.
285  $element = $this->findField($name, $group);
286 
287  // If the field element was not found return false.
288  if (!$element)
289  {
290  return false;
291  }
292 
293  return $this->loadField($element, $group, $value);
294  }
295 
296  /**
297  * Method to get an attribute value from a field XML element. If the attribute doesn't exist or
298  * is null then the optional default value will be used.
299  *
300  * @param string $name The name of the form field for which to get the attribute value.
301  * @param string $attribute The name of the attribute for which to get a value.
302  * @param mixed $default The optional default value to use if no attribute value exists.
303  * @param string $group The optional dot-separated form group path on which to find the field.
304  *
305  * @return mixed The attribute value for the field.
306  *
307  * @since 11.1
308  * @throws UnexpectedValueException
309  */
310  public function getFieldAttribute($name, $attribute, $default = null, $group = null)
311  {
312  // Make sure there is a valid JForm XML document.
313  if (!($this->xml instanceof SimpleXMLElement))
314  {
315  throw new UnexpectedValueException(sprintf('%s::getFieldAttribute `xml` is not an instance of SimpleXMLElement', get_class($this)));
316  }
317 
318  // Find the form field element from the definition.
319  $element = $this->findField($name, $group);
320 
321  // If the element exists and the attribute exists for the field return the attribute value.
322  if (($element instanceof SimpleXMLElement) && ((string) $element[$attribute]))
323  {
324  return (string) $element[$attribute];
325  }
326 
327  // Otherwise return the given default value.
328  else
329  {
330  return $default;
331  }
332  }
333 
334  /**
335  * Method to get an array of JFormField objects in a given fieldset by name. If no name is
336  * given then all fields are returned.
337  *
338  * @param string $set The optional name of the fieldset.
339  *
340  * @return array The array of JFormField objects in the fieldset.
341  *
342  * @since 11.1
343  */
344  public function getFieldset($set = null)
345  {
346  $fields = array();
347 
348  // Get all of the field elements in the fieldset.
349  if ($set)
350  {
351  $elements = $this->findFieldsByFieldset($set);
352  }
353 
354  // Get all fields.
355  else
356  {
357  $elements = $this->findFieldsByGroup();
358  }
359 
360  // If no field elements were found return empty.
361  if (empty($elements))
362  {
363  return $fields;
364  }
365 
366  // Build the result array from the found field elements.
367  foreach ($elements as $element)
368  {
369  // Get the field groups for the element.
370  $attrs = $element->xpath('ancestor::fields[@name]/@name');
371  $groups = array_map('strval', $attrs ? $attrs : array());
372  $group = implode('.', $groups);
373 
374  // If the field is successfully loaded add it to the result array.
375  if ($field = $this->loadField($element, $group))
376  {
377  $fields[$field->id] = $field;
378  }
379  }
380 
381  return $fields;
382  }
383 
384  /**
385  * Method to get an array of fieldset objects optionally filtered over a given field group.
386  *
387  * @param string $group The dot-separated form group path on which to filter the fieldsets.
388  *
389  * @return array The array of fieldset objects.
390  *
391  * @since 11.1
392  */
393  public function getFieldsets($group = null)
394  {
395  $fieldsets = array();
396  $sets = array();
397 
398  // Make sure there is a valid JForm XML document.
399  if (!($this->xml instanceof SimpleXMLElement))
400  {
401  return $fieldsets;
402  }
403 
404  if ($group)
405  {
406  // Get the fields elements for a given group.
407  $elements = &$this->findGroup($group);
408 
409  foreach ($elements as &$element)
410  {
411  // Get an array of <fieldset /> elements and fieldset attributes within the fields element.
412  if ($tmp = $element->xpath('descendant::fieldset[@name] | descendant::field[@fieldset]/@fieldset'))
413  {
414  $sets = array_merge($sets, (array) $tmp);
415  }
416  }
417  }
418  else
419  {
420  // Get an array of <fieldset /> elements and fieldset attributes.
421  $sets = $this->xml->xpath('//fieldset[@name] | //field[@fieldset]/@fieldset');
422  }
423 
424  // If no fieldsets are found return empty.
425  if (empty($sets))
426  {
427  return $fieldsets;
428  }
429 
430  // Process each found fieldset.
431  foreach ($sets as $set)
432  {
433  // Are we dealing with a fieldset element?
434  if ((string) $set['name'])
435  {
436  // Only create it if it doesn't already exist.
437  if (empty($fieldsets[(string) $set['name']]))
438  {
439  // Build the fieldset object.
440  $fieldset = (object) array('name' => '', 'label' => '', 'description' => '');
441 
442  foreach ($set->attributes() as $name => $value)
443  {
444  $fieldset->$name = (string) $value;
445  }
446 
447  // Add the fieldset object to the list.
448  $fieldsets[$fieldset->name] = $fieldset;
449  }
450  }
451 
452  // Must be dealing with a fieldset attribute.
453  else
454  {
455  // Only create it if it doesn't already exist.
456  if (empty($fieldsets[(string) $set]))
457  {
458  // Attempt to get the fieldset element for data (throughout the entire form document).
459  $tmp = $this->xml->xpath('//fieldset[@name="' . (string) $set . '"]');
460 
461  // If no element was found, build a very simple fieldset object.
462  if (empty($tmp))
463  {
464  $fieldset = (object) array('name' => (string) $set, 'label' => '', 'description' => '');
465  }
466 
467  // Build the fieldset object from the element.
468  else
469  {
470  $fieldset = (object) array('name' => '', 'label' => '', 'description' => '');
471 
472  foreach ($tmp[0]->attributes() as $name => $value)
473  {
474  $fieldset->$name = (string) $value;
475  }
476  }
477 
478  // Add the fieldset object to the list.
479  $fieldsets[$fieldset->name] = $fieldset;
480  }
481  }
482  }
483 
484  return $fieldsets;
485  }
486 
487  /**
488  * Method to get the form control. This string serves as a container for all form fields. For
489  * example, if there is a field named 'foo' and a field named 'bar' and the form control is
490  * empty the fields will be rendered like: <input name="foo" /> and <input name="bar" />. If
491  * the form control is set to 'joomla' however, the fields would be rendered like:
492  * <input name="joomla[foo]" /> and <input name="joomla[bar]" />.
493  *
494  * @return string The form control string.
495  *
496  * @since 11.1
497  */
498  public function getFormControl()
499  {
500  return (string) $this->options['control'];
501  }
502 
503  /**
504  * Method to get an array of JFormField objects in a given field group by name.
505  *
506  * @param string $group The dot-separated form group path for which to get the form fields.
507  * @param boolean $nested True to also include fields in nested groups that are inside of the
508  * group for which to find fields.
509  *
510  * @return array The array of JFormField objects in the field group.
511  *
512  * @since 11.1
513  */
514  public function getGroup($group, $nested = false)
515  {
516  $fields = array();
517 
518  // Get all of the field elements in the field group.
519  $elements = $this->findFieldsByGroup($group, $nested);
520 
521  // If no field elements were found return empty.
522  if (empty($elements))
523  {
524  return $fields;
525  }
526 
527  // Build the result array from the found field elements.
528  foreach ($elements as $element)
529  {
530  // Get the field groups for the element.
531  $attrs = $element->xpath('ancestor::fields[@name]/@name');
532  $groups = array_map('strval', $attrs ? $attrs : array());
533  $group = implode('.', $groups);
534 
535  // If the field is successfully loaded add it to the result array.
536  if ($field = $this->loadField($element, $group))
537  {
538  $fields[$field->id] = $field;
539  }
540  }
541 
542  return $fields;
543  }
544 
545  /**
546  * Method to get a form field markup for the field input.
547  *
548  * @param string $name The name of the form field.
549  * @param string $group The optional dot-separated form group path on which to find the field.
550  * @param mixed $value The optional value to use as the default for the field.
551  *
552  * @return string The form field markup.
553  *
554  * @since 11.1
555  */
556  public function getInput($name, $group = null, $value = null)
557  {
558  // Attempt to get the form field.
559  if ($field = $this->getField($name, $group, $value))
560  {
561  return $field->input;
562  }
563 
564  return '';
565  }
566 
567  /**
568  * Method to get the label for a field input.
569  *
570  * @param string $name The name of the form field.
571  * @param string $group The optional dot-separated form group path on which to find the field.
572  *
573  * @return string The form field label.
574  *
575  * @since 11.1
576  */
577  public function getLabel($name, $group = null)
578  {
579  // Attempt to get the form field.
580  if ($field = $this->getField($name, $group))
581  {
582  return $field->label;
583  }
584 
585  return '';
586  }
587 
588  /**
589  * Method to get the form name.
590  *
591  * @return string The name of the form.
592  *
593  * @since 11.1
594  */
595  public function getName()
596  {
597  return $this->name;
598  }
599 
600  /**
601  * Method to get the value of a field.
602  *
603  * @param string $name The name of the field for which to get the value.
604  * @param string $group The optional dot-separated form group path on which to get the value.
605  * @param mixed $default The optional default value of the field value is empty.
606  *
607  * @return mixed The value of the field or the default value if empty.
608  *
609  * @since 11.1
610  */
611  public function getValue($name, $group = null, $default = null)
612  {
613  // If a group is set use it.
614  if ($group)
615  {
616  $return = $this->data->get($group . '.' . $name, $default);
617  }
618  else
619  {
620  $return = $this->data->get($name, $default);
621  }
622 
623  return $return;
624  }
625 
626  /**
627  * Method to get a control group with label and input.
628  *
629  * @param string $name The name of the field for which to get the value.
630  * @param string $group The optional dot-separated form group path on which to get the value.
631  * @param mixed $default The optional default value of the field value is empty.
632  *
633  * @return string A string containing the html for the control goup
634  *
635  * @since 3.2
636  */
637  public function getControlGroup($name, $group = null, $default = null)
638  {
639  $field = $this->getField($name, $group, $default);
640  if ($field)
641  {
642  return $field->getControlGroup();
643  }
644  return '';
645  }
646 
647  /**
648  * Method to get all control groups with label and input of a fieldset.
649  *
650  * @param string $name The name of the fieldset for which to get the values.
651  *
652  * @return string A string containing the html for the control goups
653  *
654  * @since 3.2
655  */
656  public function getControlGroups($name)
657  {
658  $fields = $this->getFieldset($name);
659 
660  $html = array();
661  foreach ($fields as $field)
662  {
663  $html[] = $field->getControlGroup();
664  }
665 
666  return implode('', $html);
667  }
668 
669  /**
670  * Method to load the form description from an XML string or object.
671  *
672  * The replace option works per field. If a field being loaded already exists in the current
673  * form definition then the behavior or load will vary depending upon the replace flag. If it
674  * is set to true, then the existing field will be replaced in its exact location by the new
675  * field being loaded. If it is false, then the new field being loaded will be ignored and the
676  * method will move on to the next field to load.
677  *
678  * @param string $data The name of an XML string or object.
679  * @param string $replace Flag to toggle whether form fields should be replaced if a field
680  * already exists with the same group/name.
681  * @param string $xpath An optional xpath to search for the fields.
682  *
683  * @return boolean True on success, false otherwise.
684  *
685  * @since 11.1
686  */
687  public function load($data, $replace = true, $xpath = false)
688  {
689  // If the data to load isn't already an XML element or string return false.
690  if ((!($data instanceof SimpleXMLElement)) && (!is_string($data)))
691  {
692  return false;
693  }
694 
695  // Attempt to load the XML if a string.
696  if (is_string($data))
697  {
698  try
699  {
700  $data = new SimpleXMLElement($data);
701  }
702  catch (Exception $e)
703  {
704  return false;
705  }
706 
707  // Make sure the XML loaded correctly.
708  if (!$data)
709  {
710  return false;
711  }
712  }
713 
714  // If we have no XML definition at this point let's make sure we get one.
715  if (empty($this->xml))
716  {
717  // If no XPath query is set to search for fields, and we have a <form />, set it and return.
718  if (!$xpath && ($data->getName() == 'form'))
719  {
720  $this->xml = $data;
721 
722  // Synchronize any paths found in the load.
723  $this->syncPaths();
724 
725  return true;
726  }
727 
728  // Create a root element for the form.
729  else
730  {
731  $this->xml = new SimpleXMLElement('<form></form>');
732  }
733  }
734 
735  // Get the XML elements to load.
736  $elements = array();
737 
738  if ($xpath)
739  {
740  $elements = $data->xpath($xpath);
741  }
742  elseif ($data->getName() == 'form')
743  {
744  $elements = $data->children();
745  }
746 
747  // If there is nothing to load return true.
748  if (empty($elements))
749  {
750  return true;
751  }
752 
753  // Load the found form elements.
754  foreach ($elements as $element)
755  {
756  // Get an array of fields with the correct name.
757  $fields = $element->xpath('descendant-or-self::field');
758 
759  foreach ($fields as $field)
760  {
761  // Get the group names as strings for ancestor fields elements.
762  $attrs = $field->xpath('ancestor::fields[@name]/@name');
763  $groups = array_map('strval', $attrs ? $attrs : array());
764 
765  // Check to see if the field exists in the current form.
766  if ($current = $this->findField((string) $field['name'], implode('.', $groups)))
767  {
768  // If set to replace found fields, replace the data and remove the field so we don't add it twice.
769  if ($replace)
770  {
771  $olddom = dom_import_simplexml($current);
772  $loadeddom = dom_import_simplexml($field);
773  $addeddom = $olddom->ownerDocument->importNode($loadeddom);
774  $olddom->parentNode->replaceChild($addeddom, $olddom);
775  $loadeddom->parentNode->removeChild($loadeddom);
776  }
777  else
778  {
779  unset($field);
780  }
781  }
782  }
783 
784  // Merge the new field data into the existing XML document.
785  self::addNode($this->xml, $element);
786  }
787 
788  // Synchronize any paths found in the load.
789  $this->syncPaths();
790 
791  return true;
792  }
793 
794  /**
795  * Method to load the form description from an XML file.
796  *
797  * The reset option works on a group basis. If the XML file references
798  * groups that have already been created they will be replaced with the
799  * fields in the new XML file unless the $reset parameter has been set
800  * to false.
801  *
802  * @param string $file The filesystem path of an XML file.
803  * @param string $reset Flag to toggle whether form fields should be replaced if a field
804  * already exists with the same group/name.
805  * @param string $xpath An optional xpath to search for the fields.
806  *
807  * @return boolean True on success, false otherwise.
808  *
809  * @since 11.1
810  */
811  public function loadFile($file, $reset = true, $xpath = false)
812  {
813  // Check to see if the path is an absolute path.
814  if (!is_file($file))
815  {
816  // Not an absolute path so let's attempt to find one using JPath.
817  $file = JPath::find(self::addFormPath(), strtolower($file) . '.xml');
818 
819  // If unable to find the file return false.
820  if (!$file)
821  {
822  return false;
823  }
824  }
825 
826  // Attempt to load the XML file.
827  $xml = simplexml_load_file($file);
828 
829  return $this->load($xml, $reset, $xpath);
830  }
831 
832  /**
833  * Method to remove a field from the form definition.
834  *
835  * @param string $name The name of the form field for which remove.
836  * @param string $group The optional dot-separated form group path on which to find the field.
837  *
838  * @return boolean True on success.
839  *
840  * @since 11.1
841  * @throws UnexpectedValueException
842  */
843  public function removeField($name, $group = null)
844  {
845  // Make sure there is a valid JForm XML document.
846  if (!($this->xml instanceof SimpleXMLElement))
847  {
848  throw new UnexpectedValueException(sprintf('%s::getFieldAttribute `xml` is not an instance of SimpleXMLElement', get_class($this)));
849  }
850 
851  // Find the form field element from the definition.
852  $element = $this->findField($name, $group);
853 
854  // If the element exists remove it from the form definition.
855  if ($element instanceof SimpleXMLElement)
856  {
857  $dom = dom_import_simplexml($element);
858  $dom->parentNode->removeChild($dom);
859  }
860 
861  return true;
862  }
863 
864  /**
865  * Method to remove a group from the form definition.
866  *
867  * @param string $group The dot-separated form group path for the group to remove.
868  *
869  * @return boolean True on success.
870  *
871  * @since 11.1
872  * @throws UnexpectedValueException
873  */
874  public function removeGroup($group)
875  {
876  // Make sure there is a valid JForm XML document.
877  if (!($this->xml instanceof SimpleXMLElement))
878  {
879  throw new UnexpectedValueException(sprintf('%s::getFieldAttribute `xml` is not an instance of SimpleXMLElement', get_class($this)));
880  }
881 
882  // Get the fields elements for a given group.
883  $elements = &$this->findGroup($group);
884 
885  foreach ($elements as &$element)
886  {
887  $dom = dom_import_simplexml($element);
888  $dom->parentNode->removeChild($dom);
889  }
890 
891  return true;
892  }
893 
894  /**
895  * Method to reset the form data store and optionally the form XML definition.
896  *
897  * @param boolean $xml True to also reset the XML form definition.
898  *
899  * @return boolean True on success.
900  *
901  * @since 11.1
902  */
903  public function reset($xml = false)
904  {
905  unset($this->data);
906  $this->data = new JRegistry;
907 
908  if ($xml)
909  {
910  unset($this->xml);
911  $this->xml = new SimpleXMLElement('<form></form>');
912  }
913 
914  return true;
915  }
916 
917  /**
918  * Method to set a field XML element to the form definition. If the replace flag is set then
919  * the field will be set whether it already exists or not. If it isn't set, then the field
920  * will not be replaced if it already exists.
921  *
922  * @param SimpleXMLElement $element The XML element object representation of the form field.
923  * @param string $group The optional dot-separated form group path on which to set the field.
924  * @param boolean $replace True to replace an existing field if one already exists.
925  *
926  * @return boolean True on success.
927  *
928  * @since 11.1
929  * @throws UnexpectedValueException
930  */
931  public function setField(SimpleXMLElement $element, $group = null, $replace = true)
932  {
933  // Make sure there is a valid JForm XML document.
934  if (!($this->xml instanceof SimpleXMLElement))
935  {
936  throw new UnexpectedValueException(sprintf('%s::getFieldAttribute `xml` is not an instance of SimpleXMLElement', get_class($this)));
937  }
938 
939  // Find the form field element from the definition.
940  $old = $this->findField((string) $element['name'], $group);
941 
942  // If an existing field is found and replace flag is false do nothing and return true.
943  if (!$replace && !empty($old))
944  {
945  return true;
946  }
947 
948  // If an existing field is found and replace flag is true remove the old field.
949  if ($replace && !empty($old) && ($old instanceof SimpleXMLElement))
950  {
951  $dom = dom_import_simplexml($old);
952  $dom->parentNode->removeChild($dom);
953  }
954 
955  // If no existing field is found find a group element and add the field as a child of it.
956  if ($group)
957  {
958  // Get the fields elements for a given group.
959  $fields = &$this->findGroup($group);
960 
961  // If an appropriate fields element was found for the group, add the element.
962  if (isset($fields[0]) && ($fields[0] instanceof SimpleXMLElement))
963  {
964  self::addNode($fields[0], $element);
965  }
966  }
967  else
968  {
969  // Set the new field to the form.
970  self::addNode($this->xml, $element);
971  }
972 
973  // Synchronize any paths found in the load.
974  $this->syncPaths();
975 
976  return true;
977  }
978 
979  /**
980  * Method to set an attribute value for a field XML element.
981  *
982  * @param string $name The name of the form field for which to set the attribute value.
983  * @param string $attribute The name of the attribute for which to set a value.
984  * @param mixed $value The value to set for the attribute.
985  * @param string $group The optional dot-separated form group path on which to find the field.
986  *
987  * @return boolean True on success.
988  *
989  * @since 11.1
990  * @throws UnexpectedValueException
991  */
992  public function setFieldAttribute($name, $attribute, $value, $group = null)
993  {
994  // Make sure there is a valid JForm XML document.
995  if (!($this->xml instanceof SimpleXMLElement))
996  {
997  throw new UnexpectedValueException(sprintf('%s::getFieldAttribute `xml` is not an instance of SimpleXMLElement', get_class($this)));
998  }
999 
1000  // Find the form field element from the definition.
1001  $element = $this->findField($name, $group);
1002 
1003  // If the element doesn't exist return false.
1004  if (!($element instanceof SimpleXMLElement))
1005  {
1006  return false;
1007  }
1008 
1009  // Otherwise set the attribute and return true.
1010  else
1011  {
1012  $element[$attribute] = $value;
1013 
1014  // Synchronize any paths found in the load.
1015  $this->syncPaths();
1016 
1017  return true;
1018  }
1019  }
1020 
1021  /**
1022  * Method to set some field XML elements to the form definition. If the replace flag is set then
1023  * the fields will be set whether they already exists or not. If it isn't set, then the fields
1024  * will not be replaced if they already exist.
1025  *
1026  * @param array &$elements The array of XML element object representations of the form fields.
1027  * @param string $group The optional dot-separated form group path on which to set the fields.
1028  * @param boolean $replace True to replace existing fields if they already exist.
1029  *
1030  * @return boolean True on success.
1031  *
1032  * @since 11.1
1033  * @throws UnexpectedValueException
1034  */
1035  public function setFields(&$elements, $group = null, $replace = true)
1036  {
1037  // Make sure there is a valid JForm XML document.
1038  if (!($this->xml instanceof SimpleXMLElement))
1039  {
1040  throw new UnexpectedValueException(sprintf('%s::getFieldAttribute `xml` is not an instance of SimpleXMLElement', get_class($this)));
1041  }
1042 
1043  // Make sure the elements to set are valid.
1044  foreach ($elements as $element)
1045  {
1046  if (!($element instanceof SimpleXMLElement))
1047  {
1048  throw new UnexpectedValueException(sprintf('$element not SimpleXMLElement in %s::setFields', get_class($this)));
1049  }
1050  }
1051 
1052  // Set the fields.
1053  $return = true;
1054 
1055  foreach ($elements as $element)
1056  {
1057  if (!$this->setField($element, $group, $replace))
1058  {
1059  $return = false;
1060  }
1061  }
1062 
1063  // Synchronize any paths found in the load.
1064  $this->syncPaths();
1065 
1066  return $return;
1067  }
1068 
1069  /**
1070  * Method to set the value of a field. If the field does not exist in the form then the method
1071  * will return false.
1072  *
1073  * @param string $name The name of the field for which to set the value.
1074  * @param string $group The optional dot-separated form group path on which to find the field.
1075  * @param mixed $value The value to set for the field.
1076  *
1077  * @return boolean True on success.
1078  *
1079  * @since 11.1
1080  */
1081  public function setValue($name, $group = null, $value = null)
1082  {
1083  // If the field does not exist return false.
1084  if (!$this->findField($name, $group))
1085  {
1086  return false;
1087  }
1088 
1089  // If a group is set use it.
1090  if ($group)
1091  {
1092  $this->data->set($group . '.' . $name, $value);
1093  }
1094  else
1095  {
1096  $this->data->set($name, $value);
1097  }
1098 
1099  return true;
1100  }
1101 
1102  /**
1103  * Method to validate form data.
1104  *
1105  * Validation warnings will be pushed into JForm::errors and should be
1106  * retrieved with JForm::getErrors() when validate returns boolean false.
1107  *
1108  * @param array $data An array of field values to validate.
1109  * @param string $group The optional dot-separated form group path on which to filter the
1110  * fields to be validated.
1111  *
1112  * @return mixed True on sucess.
1113  *
1114  * @since 11.1
1115  */
1116  public function validate($data, $group = null)
1117  {
1118  // Make sure there is a valid JForm XML document.
1119  if (!($this->xml instanceof SimpleXMLElement))
1120  {
1121  return false;
1122  }
1123 
1124  $return = true;
1125 
1126  // Create an input registry object from the data to validate.
1127  $input = new JRegistry($data);
1128 
1129  // Get the fields for which to validate the data.
1130  $fields = $this->findFieldsByGroup($group);
1131 
1132  if (!$fields)
1133  {
1134  // PANIC!
1135  return false;
1136  }
1137 
1138  // Validate the fields.
1139  foreach ($fields as $field)
1140  {
1141  $value = null;
1142  $name = (string) $field['name'];
1143 
1144  // Get the group names as strings for ancestor fields elements.
1145  $attrs = $field->xpath('ancestor::fields[@name]/@name');
1146  $groups = array_map('strval', $attrs ? $attrs : array());
1147  $group = implode('.', $groups);
1148 
1149  // Get the value from the input data.
1150  if ($group)
1151  {
1152  $value = $input->get($group . '.' . $name);
1153  }
1154  else
1155  {
1156  $value = $input->get($name);
1157  }
1158 
1159  // Validate the field.
1160  $valid = $this->validateField($field, $group, $value, $input);
1161 
1162  // Check for an error.
1163  if ($valid instanceof Exception)
1164  {
1165  array_push($this->errors, $valid);
1166  $return = false;
1167  }
1168  }
1169 
1170  return $return;
1171  }
1172 
1173  /**
1174  * Method to apply an input filter to a value based on field data.
1175  *
1176  * @param string $element The XML element object representation of the form field.
1177  * @param mixed $value The value to filter for the field.
1178  *
1179  * @return mixed The filtered value.
1180  *
1181  * @since 11.1
1182  */
1183  protected function filterField($element, $value)
1184  {
1185  // Make sure there is a valid SimpleXMLElement.
1186  if (!($element instanceof SimpleXMLElement))
1187  {
1188  return false;
1189  }
1190 
1191  // Get the field filter type.
1192  $filter = (string) $element['filter'];
1193 
1194  // Process the input value based on the filter.
1195  $return = null;
1196 
1197  switch (strtoupper($filter))
1198  {
1199  // Access Control Rules.
1200  case 'RULES':
1201  $return = array();
1202 
1203  foreach ((array) $value as $action => $ids)
1204  {
1205  // Build the rules array.
1206  $return[$action] = array();
1207 
1208  foreach ($ids as $id => $p)
1209  {
1210  if ($p !== '')
1211  {
1212  $return[$action][$id] = ($p == '1' || $p == 'true') ? true : false;
1213  }
1214  }
1215  }
1216  break;
1217 
1218  // Do nothing, thus leaving the return value as null.
1219  case 'UNSET':
1220  break;
1221 
1222  // No Filter.
1223  case 'RAW':
1224  $return = $value;
1225  break;
1226 
1227  // Filter the input as an array of integers.
1228  case 'INT_ARRAY':
1229  // Make sure the input is an array.
1230  if (is_object($value))
1231  {
1232  $value = get_object_vars($value);
1233  }
1234 
1235  $value = is_array($value) ? $value : array($value);
1236 
1237  JArrayHelper::toInteger($value);
1238  $return = $value;
1239  break;
1240 
1241  // Filter safe HTML.
1242  case 'SAFEHTML':
1243  $return = JFilterInput::getInstance(null, null, 1, 1)->clean($value, 'string');
1244  break;
1245 
1246  // Convert a date to UTC based on the server timezone offset.
1247  case 'SERVER_UTC':
1248  if ((int) $value > 0)
1249  {
1250  // Get the server timezone setting.
1251  $offset = JFactory::getConfig()->get('offset');
1252 
1253  // Return an SQL formatted datetime string in UTC.
1254  $return = JFactory::getDate($value, $offset)->toSql();
1255  }
1256  else
1257  {
1258  $return = '';
1259  }
1260  break;
1261 
1262  // Convert a date to UTC based on the user timezone offset.
1263  case 'USER_UTC':
1264  if ((int) $value > 0)
1265  {
1266  // Get the user timezone setting defaulting to the server timezone setting.
1267  $offset = JFactory::getUser()->getParam('timezone', JFactory::getConfig()->get('offset'));
1268 
1269  // Return a MySQL formatted datetime string in UTC.
1270  $return = JFactory::getDate($value, $offset)->toSql();
1271  }
1272  else
1273  {
1274  $return = '';
1275  }
1276  break;
1277 
1278  // Ensures a protocol is present in the saved field. Only use when
1279  // the only permitted protocols requre '://'. See JFormRuleUrl for list of these.
1280 
1281  case 'URL':
1282  if (empty($value))
1283  {
1284  return false;
1285  }
1286 
1287  $value = JFilterInput::getInstance()->clean($value, 'html');
1288  $value = trim($value);
1289 
1290  // Check for a protocol
1291  $protocol = parse_url($value, PHP_URL_SCHEME);
1292 
1293  // If there is no protocol and the relative option is not specified,
1294  // we assume that it is an external URL and prepend http://.
1295  if (($element['type'] == 'url' && !$protocol && !$element['relative'])
1296  || (!$element['type'] == 'url' && !$protocol))
1297  {
1298  $protocol = 'http';
1299 
1300  // If it looks like an internal link, then add the root.
1301  if (substr($value, 0) == 'index.php')
1302  {
1303  $value = JUri::root() . $value;
1304  }
1305 
1306  // Otherwise we treat it is an external link.
1307  // Put the url back together.
1308  $value = $protocol . '://' . $value;
1309  }
1310 
1311  // If relative URLS are allowed we assume that URLs without protocols are internal.
1312  elseif (!$protocol && $element['relative'])
1313  {
1314  $host = JUri::getInstance('SERVER')->gethost();
1315 
1316  // If it starts with the host string, just prepend the protocol.
1317  if (substr($value, 0) == $host)
1318  {
1319  $value = 'http://' . $value;
1320  }
1321 
1322  // Otherwise prepend the root.
1323  else
1324  {
1325  $value = JUri::root() . $value;
1326  }
1327  }
1328 
1329  $value = JStringPunycode::urlToPunycode($value);
1330  $return = $value;
1331  break;
1332 
1333  case 'TEL':
1334  $value = trim($value);
1335 
1336  // Does it match the NANP pattern?
1337  if (preg_match('/^(?:\+?1[-. ]?)?\(?([2-9][0-8][0-9])\)?[-. ]?([2-9][0-9]{2})[-. ]?([0-9]{4})$/', $value) == 1)
1338  {
1339  $number = (string) preg_replace('/[^\d]/', '', $value);
1340 
1341  if (substr($number, 0, 1) == 1)
1342  {
1343  $number = substr($number, 1);
1344  }
1345 
1346  if (substr($number, 0, 2) == '+1')
1347  {
1348  $number = substr($number, 2);
1349  }
1350 
1351  $result = '1.' . $number;
1352  }
1353 
1354  // If not, does it match ITU-T?
1355  elseif (preg_match('/^\+(?:[0-9] ?){6,14}[0-9]$/', $value) == 1)
1356  {
1357  $countrycode = substr($value, 0, strpos($value, ' '));
1358  $countrycode = (string) preg_replace('/[^\d]/', '', $countrycode);
1359  $number = strstr($value, ' ');
1360  $number = (string) preg_replace('/[^\d]/', '', $number);
1361  $result = $countrycode . '.' . $number;
1362  }
1363 
1364  // If not, does it match EPP?
1365  elseif (preg_match('/^\+[0-9]{1,3}\.[0-9]{4,14}(?:x.+)?$/', $value) == 1)
1366  {
1367  if (strstr($value, 'x'))
1368  {
1369  $xpos = strpos($value, 'x');
1370  $value = substr($value, 0, $xpos);
1371  }
1372 
1373  $result = str_replace('+', '', $value);
1374  }
1375 
1376  // Maybe it is already ccc.nnnnnnn?
1377  elseif (preg_match('/[0-9]{1,3}\.[0-9]{4,14}$/', $value) == 1)
1378  {
1379  $result = $value;
1380  }
1381 
1382  // If not, can we make it a string of digits?
1383  else
1384  {
1385  $value = (string) preg_replace('/[^\d]/', '', $value);
1386 
1387  if ($value != null && strlen($value) <= 15)
1388  {
1389  $length = strlen($value);
1390 
1391  // If it is fewer than 13 digits assume it is a local number
1392  if ($length <= 12)
1393  {
1394  $result = '.' . $value;
1395  }
1396  else
1397  {
1398  // If it has 13 or more digits let's make a country code.
1399  $cclen = $length - 12;
1400  $result = substr($value, 0, $cclen) . '.' . substr($value, $cclen);
1401  }
1402  }
1403 
1404  // If not let's not save anything.
1405  else
1406  {
1407  $result = '';
1408  }
1409  }
1410 
1411  $return = $result;
1412 
1413  break;
1414  default:
1415  // Check for a callback filter.
1416  if (strpos($filter, '::') !== false && is_callable(explode('::', $filter)))
1417  {
1418  $return = call_user_func(explode('::', $filter), $value);
1419  }
1420 
1421  // Filter using a callback function if specified.
1422  elseif (function_exists($filter))
1423  {
1424  $return = call_user_func($filter, $value);
1425  }
1426 
1427  // Filter using JFilterInput. All HTML code is filtered by default.
1428  else
1429  {
1430  $return = JFilterInput::getInstance()->clean($value, $filter);
1431  }
1432  break;
1433  }
1434 
1435  return $return;
1436  }
1437 
1438  /**
1439  * Method to get a form field represented as an XML element object.
1440  *
1441  * @param string $name The name of the form field.
1442  * @param string $group The optional dot-separated form group path on which to find the field.
1443  *
1444  * @return mixed The XML element object for the field or boolean false on error.
1445  *
1446  * @since 11.1
1447  */
1448  protected function findField($name, $group = null)
1449  {
1450  $element = false;
1451  $fields = array();
1452 
1453  // Make sure there is a valid JForm XML document.
1454  if (!($this->xml instanceof SimpleXMLElement))
1455  {
1456  return false;
1457  }
1458 
1459  // Let's get the appropriate field element based on the method arguments.
1460  if ($group)
1461  {
1462  // Get the fields elements for a given group.
1463  $elements = &$this->findGroup($group);
1464 
1465  // Get all of the field elements with the correct name for the fields elements.
1466  foreach ($elements as $element)
1467  {
1468  // If there are matching field elements add them to the fields array.
1469  if ($tmp = $element->xpath('descendant::field[@name="' . $name . '"]'))
1470  {
1471  $fields = array_merge($fields, $tmp);
1472  }
1473  }
1474 
1475  // Make sure something was found.
1476  if (!$fields)
1477  {
1478  return false;
1479  }
1480 
1481  // Use the first correct match in the given group.
1482  $groupNames = explode('.', $group);
1483 
1484  foreach ($fields as &$field)
1485  {
1486  // Get the group names as strings for ancestor fields elements.
1487  $attrs = $field->xpath('ancestor::fields[@name]/@name');
1488  $names = array_map('strval', $attrs ? $attrs : array());
1489 
1490  // If the field is in the exact group use it and break out of the loop.
1491  if ($names == (array) $groupNames)
1492  {
1493  $element = &$field;
1494  break;
1495  }
1496  }
1497  }
1498  else
1499  {
1500  // Get an array of fields with the correct name.
1501  $fields = $this->xml->xpath('//field[@name="' . $name . '"]');
1502 
1503  // Make sure something was found.
1504  if (!$fields)
1505  {
1506  return false;
1507  }
1508 
1509  // Search through the fields for the right one.
1510  foreach ($fields as &$field)
1511  {
1512  // If we find an ancestor fields element with a group name then it isn't what we want.
1513  if ($field->xpath('ancestor::fields[@name]'))
1514  {
1515  continue;
1516  }
1517 
1518  // Found it!
1519  else
1520  {
1521  $element = &$field;
1522  break;
1523  }
1524  }
1525  }
1526 
1527  return $element;
1528  }
1529 
1530  /**
1531  * Method to get an array of <field /> elements from the form XML document which are
1532  * in a specified fieldset by name.
1533  *
1534  * @param string $name The name of the fieldset.
1535  *
1536  * @return mixed Boolean false on error or array of SimpleXMLElement objects.
1537  *
1538  * @since 11.1
1539  */
1540  protected function &findFieldsByFieldset($name)
1541  {
1542  $false = false;
1543 
1544  // Make sure there is a valid JForm XML document.
1545  if (!($this->xml instanceof SimpleXMLElement))
1546  {
1547  return $false;
1548  }
1549 
1550  /*
1551  * Get an array of <field /> elements that are underneath a <fieldset /> element
1552  * with the appropriate name attribute, and also any <field /> elements with
1553  * the appropriate fieldset attribute. To allow repeatable elements only immediate
1554  * field descendants of the fieldset are selected.
1555  */
1556  $fields = $this->xml->xpath('//fieldset[@name="' . $name . '"]/field | //field[@fieldset="' . $name . '"]');
1557 
1558  return $fields;
1559  }
1560 
1561  /**
1562  * Method to get an array of <field /> elements from the form XML document which are
1563  * in a control group by name.
1564  *
1565  * @param mixed $group The optional dot-separated form group path on which to find the fields.
1566  * Null will return all fields. False will return fields not in a group.
1567  * @param boolean $nested True to also include fields in nested groups that are inside of the
1568  * group for which to find fields.
1569  *
1570  * @return mixed Boolean false on error or array of SimpleXMLElement objects.
1571  *
1572  * @since 11.1
1573  */
1574  protected function &findFieldsByGroup($group = null, $nested = false)
1575  {
1576  $false = false;
1577  $fields = array();
1578 
1579  // Make sure there is a valid JForm XML document.
1580  if (!($this->xml instanceof SimpleXMLElement))
1581  {
1582  return $false;
1583  }
1584 
1585  // Get only fields in a specific group?
1586  if ($group)
1587  {
1588  // Get the fields elements for a given group.
1589  $elements = &$this->findGroup($group);
1590 
1591  // Get all of the field elements for the fields elements.
1592  foreach ($elements as $element)
1593  {
1594  // If there are field elements add them to the return result.
1595  if ($tmp = $element->xpath('descendant::field'))
1596  {
1597  // If we also want fields in nested groups then just merge the arrays.
1598  if ($nested)
1599  {
1600  $fields = array_merge($fields, $tmp);
1601  }
1602 
1603  // If we want to exclude nested groups then we need to check each field.
1604  else
1605  {
1606  $groupNames = explode('.', $group);
1607 
1608  foreach ($tmp as $field)
1609  {
1610  // Get the names of the groups that the field is in.
1611  $attrs = $field->xpath('ancestor::fields[@name]/@name');
1612  $names = array_map('strval', $attrs ? $attrs : array());
1613 
1614  // If the field is in the specific group then add it to the return list.
1615  if ($names == (array) $groupNames)
1616  {
1617  $fields = array_merge($fields, array($field));
1618  }
1619  }
1620  }
1621  }
1622  }
1623  }
1624  elseif ($group === false)
1625  {
1626  // Get only field elements not in a group.
1627  $fields = $this->xml->xpath('descendant::fields[not(@name)]/field | descendant::fields[not(@name)]/fieldset/field ');
1628  }
1629  else
1630  {
1631  // Get an array of all the <field /> elements.
1632  $fields = $this->xml->xpath('//field');
1633  }
1634 
1635  return $fields;
1636  }
1637 
1638  /**
1639  * Method to get a form field group represented as an XML element object.
1640  *
1641  * @param string $group The dot-separated form group path on which to find the group.
1642  *
1643  * @return mixed An array of XML element objects for the group or boolean false on error.
1644  *
1645  * @since 11.1
1646  */
1647  protected function &findGroup($group)
1648  {
1649  $false = false;
1650  $groups = array();
1651  $tmp = array();
1652 
1653  // Make sure there is a valid JForm XML document.
1654  if (!($this->xml instanceof SimpleXMLElement))
1655  {
1656  return $false;
1657  }
1658 
1659  // Make sure there is actually a group to find.
1660  $group = explode('.', $group);
1661 
1662  if (!empty($group))
1663  {
1664  // Get any fields elements with the correct group name.
1665  $elements = $this->xml->xpath('//fields[@name="' . (string) $group[0] . '"]');
1666 
1667  // Check to make sure that there are no parent groups for each element.
1668  foreach ($elements as $element)
1669  {
1670  if (!$element->xpath('ancestor::fields[@name]'))
1671  {
1672  $tmp[] = $element;
1673  }
1674  }
1675 
1676  // Iterate through the nested groups to find any matching form field groups.
1677  for ($i = 1, $n = count($group); $i < $n; $i++)
1678  {
1679  // Initialise some loop variables.
1680  $validNames = array_slice($group, 0, $i + 1);
1681  $current = $tmp;
1682  $tmp = array();
1683 
1684  // Check to make sure that there are no parent groups for each element.
1685  foreach ($current as $element)
1686  {
1687  // Get any fields elements with the correct group name.
1688  $children = $element->xpath('descendant::fields[@name="' . (string) $group[$i] . '"]');
1689 
1690  // For the found fields elements validate that they are in the correct groups.
1691  foreach ($children as $fields)
1692  {
1693  // Get the group names as strings for ancestor fields elements.
1694  $attrs = $fields->xpath('ancestor-or-self::fields[@name]/@name');
1695  $names = array_map('strval', $attrs ? $attrs : array());
1696 
1697  // If the group names for the fields element match the valid names at this
1698  // level add the fields element.
1699  if ($validNames == $names)
1700  {
1701  $tmp[] = $fields;
1702  }
1703  }
1704  }
1705  }
1706 
1707  // Only include valid XML objects.
1708  foreach ($tmp as $element)
1709  {
1710  if ($element instanceof SimpleXMLElement)
1711  {
1712  $groups[] = $element;
1713  }
1714  }
1715  }
1716 
1717  return $groups;
1718  }
1719 
1720  /**
1721  * Method to load, setup and return a JFormField object based on field data.
1722  *
1723  * @param string $element The XML element object representation of the form field.
1724  * @param string $group The optional dot-separated form group path on which to find the field.
1725  * @param mixed $value The optional value to use as the default for the field.
1726  *
1727  * @return mixed The JFormField object for the field or boolean false on error.
1728  *
1729  * @since 11.1
1730  */
1731  protected function loadField($element, $group = null, $value = null)
1732  {
1733  // Make sure there is a valid SimpleXMLElement.
1734  if (!($element instanceof SimpleXMLElement))
1735  {
1736  return false;
1737  }
1738 
1739  // Get the field type.
1740  $type = $element['type'] ? (string) $element['type'] : 'text';
1741 
1742  // Load the JFormField object for the field.
1743  $field = $this->loadFieldType($type);
1744 
1745  // If the object could not be loaded, get a text field object.
1746  if ($field === false)
1747  {
1748  $field = $this->loadFieldType('text');
1749  }
1750 
1751  /*
1752  * Get the value for the form field if not set.
1753  * Default to the translated version of the 'default' attribute
1754  * if 'translate_default' attribute if set to 'true' or '1'
1755  * else the value of the 'default' attribute for the field.
1756  */
1757  if ($value === null)
1758  {
1759  $default = (string) $element['default'];
1760 
1761  if (($translate = $element['translate_default']) && ((string) $translate == 'true' || (string) $translate == '1'))
1762  {
1763  $lang = JFactory::getLanguage();
1764 
1765  if ($lang->hasKey($default))
1766  {
1767  $debug = $lang->setDebug(false);
1768  $default = JText::_($default);
1769  $lang->setDebug($debug);
1770  }
1771  else
1772  {
1773  $default = JText::_($default);
1774  }
1775  }
1776 
1777  $value = $this->getValue((string) $element['name'], $group, $default);
1778  }
1779 
1780  // Setup the JFormField object.
1781  $field->setForm($this);
1782 
1783  if ($field->setup($element, $value, $group))
1784  {
1785  return $field;
1786  }
1787  else
1788  {
1789  return false;
1790  }
1791  }
1792 
1793  /**
1794  * Proxy for {@link JFormHelper::loadFieldType()}.
1795  *
1796  * @param string $type The field type.
1797  * @param boolean $new Flag to toggle whether we should get a new instance of the object.
1798  *
1799  * @return mixed JFormField object on success, false otherwise.
1800  *
1801  * @since 11.1
1802  */
1803  protected function loadFieldType($type, $new = true)
1804  {
1805  return JFormHelper::loadFieldType($type, $new);
1806  }
1807 
1808  /**
1809  * Proxy for JFormHelper::loadRuleType().
1810  *
1811  * @param string $type The rule type.
1812  * @param boolean $new Flag to toggle whether we should get a new instance of the object.
1813  *
1814  * @return mixed JFormRule object on success, false otherwise.
1815  *
1816  * @see JFormHelper::loadRuleType()
1817  * @since 11.1
1818  */
1819  protected function loadRuleType($type, $new = true)
1820  {
1821  return JFormHelper::loadRuleType($type, $new);
1822  }
1823 
1824  /**
1825  * Method to synchronize any field, form or rule paths contained in the XML document.
1826  *
1827  * @return boolean True on success.
1828  *
1829  * @since 11.1
1830  * @todo Maybe we should receive all addXXXpaths attributes at once?
1831  */
1832  protected function syncPaths()
1833  {
1834  // Make sure there is a valid JForm XML document.
1835  if (!($this->xml instanceof SimpleXMLElement))
1836  {
1837  return false;
1838  }
1839 
1840  // Get any addfieldpath attributes from the form definition.
1841  $paths = $this->xml->xpath('//*[@addfieldpath]/@addfieldpath');
1842  $paths = array_map('strval', $paths ? $paths : array());
1843 
1844  // Add the field paths.
1845  foreach ($paths as $path)
1846  {
1847  $path = JPATH_ROOT . '/' . ltrim($path, '/\\');
1848  self::addFieldPath($path);
1849  }
1850 
1851  // Get any addformpath attributes from the form definition.
1852  $paths = $this->xml->xpath('//*[@addformpath]/@addformpath');
1853  $paths = array_map('strval', $paths ? $paths : array());
1854 
1855  // Add the form paths.
1856  foreach ($paths as $path)
1857  {
1858  $path = JPATH_ROOT . '/' . ltrim($path, '/\\');
1859  self::addFormPath($path);
1860  }
1861 
1862  // Get any addrulepath attributes from the form definition.
1863  $paths = $this->xml->xpath('//*[@addrulepath]/@addrulepath');
1864  $paths = array_map('strval', $paths ? $paths : array());
1865 
1866  // Add the rule paths.
1867  foreach ($paths as $path)
1868  {
1869  $path = JPATH_ROOT . '/' . ltrim($path, '/\\');
1870  self::addRulePath($path);
1871  }
1872 
1873  return true;
1874  }
1875 
1876  /**
1877  * Method to validate a JFormField object based on field data.
1878  *
1879  * @param SimpleXMLElement $element The XML element object representation of the form field.
1880  * @param string $group The optional dot-separated form group path on which to find the field.
1881  * @param mixed $value The optional value to use as the default for the field.
1882  * @param JRegistry $input An optional JRegistry object with the entire data set to validate
1883  * against the entire form.
1884  *
1885  * @return mixed Boolean true if field value is valid, Exception on failure.
1886  *
1887  * @since 11.1
1888  * @throws InvalidArgumentException
1889  * @throws UnexpectedValueException
1890  */
1891  protected function validateField(SimpleXMLElement $element, $group = null, $value = null, JRegistry $input = null)
1892  {
1893  $valid = true;
1894 
1895  // Check if the field is required.
1896  $required = ((string) $element['required'] == 'true' || (string) $element['required'] == 'required');
1897 
1898  if ($required)
1899  {
1900  // If the field is required and the value is empty return an error message.
1901  if (($value === '') || ($value === null))
1902  {
1903  if ($element['label'])
1904  {
1905  $message = JText::_($element['label']);
1906  }
1907  else
1908  {
1909  $message = JText::_($element['name']);
1910  }
1911 
1912  $message = JText::sprintf('JLIB_FORM_VALIDATE_FIELD_REQUIRED', $message);
1913 
1914  return new RuntimeException($message);
1915  }
1916  }
1917 
1918  // Get the field validation rule.
1919  if ($type = (string) $element['validate'])
1920  {
1921  // Load the JFormRule object for the field.
1922  $rule = $this->loadRuleType($type);
1923 
1924  // If the object could not be loaded return an error message.
1925  if ($rule === false)
1926  {
1927  throw new UnexpectedValueException(sprintf('%s::validateField() rule `%s` missing.', get_class($this), $type));
1928  }
1929 
1930  // Run the field validation rule test.
1931  $valid = $rule->test($element, $value, $group, $input, $this);
1932 
1933  // Check for an error in the validation test.
1934  if ($valid instanceof Exception)
1935  {
1936  return $valid;
1937  }
1938  }
1939 
1940  // Check if the field is valid.
1941  if ($valid === false)
1942  {
1943  // Does the field have a defined error message?
1944  $message = (string) $element['message'];
1945 
1946  if ($message)
1947  {
1948  $message = JText::_($element['message']);
1949 
1950  return new UnexpectedValueException($message);
1951  }
1952  else
1953  {
1954  $message = JText::_($element['label']);
1955  $message = JText::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', $message);
1956 
1957  return new UnexpectedValueException($message);
1958  }
1959  }
1960 
1961  return true;
1962  }
1963 
1964  /**
1965  * Proxy for {@link JFormHelper::addFieldPath()}.
1966  *
1967  * @param mixed $new A path or array of paths to add.
1968  *
1969  * @return array The list of paths that have been added.
1970  *
1971  * @since 11.1
1972  */
1973  public static function addFieldPath($new = null)
1974  {
1975  return JFormHelper::addFieldPath($new);
1976  }
1977 
1978  /**
1979  * Proxy for JFormHelper::addFormPath().
1980  *
1981  * @param mixed $new A path or array of paths to add.
1982  *
1983  * @return array The list of paths that have been added.
1984  *
1985  * @see JFormHelper::addFormPath()
1986  * @since 11.1
1987  */
1988  public static function addFormPath($new = null)
1989  {
1990  return JFormHelper::addFormPath($new);
1991  }
1992 
1993  /**
1994  * Proxy for JFormHelper::addRulePath().
1995  *
1996  * @param mixed $new A path or array of paths to add.
1997  *
1998  * @return array The list of paths that have been added.
1999  *
2000  * @see JFormHelper::addRulePath()
2001  * @since 11.1
2002  */
2003  public static function addRulePath($new = null)
2004  {
2005  return JFormHelper::addRulePath($new);
2006  }
2007 
2008  /**
2009  * Method to get an instance of a form.
2010  *
2011  * @param string $name The name of the form.
2012  * @param string $data The name of an XML file or string to load as the form definition.
2013  * @param array $options An array of form options.
2014  * @param string $replace Flag to toggle whether form fields should be replaced if a field
2015  * already exists with the same group/name.
2016  * @param string $xpath An optional xpath to search for the fields.
2017  *
2018  * @return object JForm instance.
2019  *
2020  * @since 11.1
2021  * @throws InvalidArgumentException if no data provided.
2022  * @throws RuntimeException if the form could not be loaded.
2023  */
2024  public static function getInstance($name, $data = null, $options = array(), $replace = true, $xpath = false)
2025  {
2026  // Reference to array with form instances
2027  $forms = &self::$forms;
2028 
2029  // Only instantiate the form if it does not already exist.
2030  if (!isset($forms[$name]))
2031  {
2032  $data = trim($data);
2033 
2034  if (empty($data))
2035  {
2036  throw new InvalidArgumentException(sprintf('JForm::getInstance(name, *%s*)', gettype($data)));
2037  }
2038 
2039  // Instantiate the form.
2040  $forms[$name] = new JForm($name, $options);
2041 
2042  // Load the data.
2043  if (substr(trim($data), 0, 1) == '<')
2044  {
2045  if ($forms[$name]->load($data, $replace, $xpath) == false)
2046  {
2047  throw new RuntimeException('JForm::getInstance could not load form');
2048  }
2049  }
2050  else
2051  {
2052  if ($forms[$name]->loadFile($data, $replace, $xpath) == false)
2053  {
2054  throw new RuntimeException('JForm::getInstance could not load file');
2055  }
2056  }
2057  }
2058 
2059  return $forms[$name];
2060  }
2061 
2062  /**
2063  * Adds a new child SimpleXMLElement node to the source.
2064  *
2065  * @param SimpleXMLElement $source The source element on which to append.
2066  * @param SimpleXMLElement $new The new element to append.
2067  *
2068  * @return void
2069  *
2070  * @since 11.1
2071  */
2072  protected static function addNode(SimpleXMLElement $source, SimpleXMLElement $new)
2073  {
2074  // Add the new child node.
2075  $node = $source->addChild($new->getName(), trim($new));
2076 
2077  // Add the attributes of the child node.
2078  foreach ($new->attributes() as $name => $value)
2079  {
2080  $node->addAttribute($name, $value);
2081  }
2082 
2083  // Add any children of the new node.
2084  foreach ($new->children() as $child)
2085  {
2086  self::addNode($node, $child);
2087  }
2088  }
2089 
2090  /**
2091  * Update the attributes of a child node
2092  *
2093  * @param SimpleXMLElement $source The source element on which to append the attributes
2094  * @param SimpleXMLElement $new The new element to append
2095  *
2096  * @return void
2097  *
2098  * @since 11.1
2099  */
2100  protected static function mergeNode(SimpleXMLElement $source, SimpleXMLElement $new)
2101  {
2102  // Update the attributes of the child node.
2103  foreach ($new->attributes() as $name => $value)
2104  {
2105  if (isset($source[$name]))
2106  {
2107  $source[$name] = (string) $value;
2108  }
2109  else
2110  {
2111  $source->addAttribute($name, $value);
2112  }
2113  }
2114  }
2115 
2116  /**
2117  * Merges new elements into a source <fields> element.
2118  *
2119  * @param SimpleXMLElement $source The source element.
2120  * @param SimpleXMLElement $new The new element to merge.
2121  *
2122  * @return void
2123  *
2124  * @since 11.1
2125  */
2126  protected static function mergeNodes(SimpleXMLElement $source, SimpleXMLElement $new)
2127  {
2128  // The assumption is that the inputs are at the same relative level.
2129  // So we just have to scan the children and deal with them.
2130 
2131  // Update the attributes of the child node.
2132  foreach ($new->attributes() as $name => $value)
2133  {
2134  if (isset($source[$name]))
2135  {
2136  $source[$name] = (string) $value;
2137  }
2138  else
2139  {
2140  $source->addAttribute($name, $value);
2141  }
2142  }
2143 
2144  foreach ($new->children() as $child)
2145  {
2146  $type = $child->getName();
2147  $name = $child['name'];
2148 
2149  // Does this node exist?
2150  $fields = $source->xpath($type . '[@name="' . $name . '"]');
2151 
2152  if (empty($fields))
2153  {
2154  // This node does not exist, so add it.
2155  self::addNode($source, $child);
2156  }
2157  else
2158  {
2159  // This node does exist.
2160  switch ($type)
2161  {
2162  case 'field':
2163  self::mergeNode($fields[0], $child);
2164  break;
2165 
2166  default:
2167  self::mergeNodes($fields[0], $child);
2168  break;
2169  }
2170  }
2171  }
2172  }
2173 
2174  /**
2175  * Returns the value of an attribute of the form itself
2176  *
2177  * @param string $name Name of the attribute to get
2178  * @param mixed $default Optional value to return if attribute not found
2179  *
2180  * @return mixed Value of the attribute / default
2181  *
2182  * @since 3.2
2183  */
2184  public function getAttribute($name, $default = null)
2185  {
2186  if ($this->xml instanceof SimpleXMLElement)
2187  {
2188  $attributes = $this->xml->attributes();
2189 
2190  // Ensure that the attribute exists
2191  if (property_exists($attributes, $name))
2192  {
2193  $value = $attributes->$name;
2194 
2195  if ($value !== null)
2196  {
2197  return (string) $value;
2198  }
2199  }
2200  }
2201 
2202  return $default;
2203  }
2204 
2205  /**
2206  * Getter for the form data
2207  *
2208  * @return JRegistry Object with the data
2209  *
2210  * @since 3.2
2211  */
2212  public function getData()
2213  {
2214  return $this->data;
2215  }
2216 
2217  /**
2218  * Method to get the XML form object
2219  *
2220  * @return SimpleXMLElement The form XML object
2221  *
2222  * @since 3.2
2223  */
2224  public function getXml()
2225  {
2226  return $this->xml;
2227  }
2228 }