PageRenderTime 61ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Joomla/Form/Form.php

https://github.com/piotr-cz/joomla-framework
PHP | 1982 lines | 1166 code | 208 blank | 608 comment | 138 complexity | 18570a59848e11aac643f2d82396f28e MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. * Part of the Joomla Framework Form Package
  4. *
  5. * @copyright Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
  6. * @license GNU General Public License version 2 or later; see LICENSE
  7. */
  8. namespace Joomla\Form;
  9. use Joomla\Filter;
  10. use Joomla\Uri\Uri;
  11. use Joomla\Language\Language;
  12. use Joomla\Language\Text;
  13. use Joomla\Filesystem\Path;
  14. use Joomla\Registry\Registry;
  15. use Joomla\Utilities\ArrayHelper;
  16. /**
  17. * Form Class for the Joomla Framework.
  18. *
  19. * This class implements a robust API for constructing, populating, filtering, and validating forms.
  20. * It uses XML definitions to construct form fields and a variety of field and rule classes to
  21. * render and validate the form.
  22. *
  23. * @link http://www.w3.org/TR/html4/interact/forms.html
  24. * @link http://www.w3.org/TR/html5/forms.html
  25. * @since 1.0
  26. */
  27. class Form
  28. {
  29. /**
  30. * The Registry data store for form fields during display.
  31. *
  32. * @var object
  33. * @since 1.0
  34. */
  35. protected $data;
  36. /**
  37. * The form object errors array.
  38. *
  39. * @var array
  40. * @since 1.0
  41. */
  42. protected $errors = array();
  43. /**
  44. * The name of the form instance.
  45. *
  46. * @var string
  47. * @since 1.0
  48. */
  49. protected $name;
  50. /**
  51. * The form object options for use in rendering and validation.
  52. *
  53. * @var array
  54. * @since 1.0
  55. */
  56. protected $options = array();
  57. /**
  58. * The form XML definition.
  59. *
  60. * @var \SimpleXMLElement
  61. * @since 1.0
  62. */
  63. protected $xml;
  64. /**
  65. * Form instances.
  66. *
  67. * @var array
  68. * @since 1.0
  69. */
  70. protected static $forms = array();
  71. /**
  72. * Method to instantiate the form object.
  73. *
  74. * @param string $name The name of the form.
  75. * @param array $options An array of form options.
  76. *
  77. * @since 1.0
  78. */
  79. public function __construct($name, array $options = array())
  80. {
  81. // Set the name for the form.
  82. $this->name = $name;
  83. // Initialise the Registry data.
  84. $this->data = new Registry;
  85. // Set the options if specified.
  86. $this->options['control'] = isset($options['control']) ? $options['control'] : false;
  87. }
  88. /**
  89. * Method to bind data to the form.
  90. *
  91. * @param mixed $data An array or object of data to bind to the form.
  92. *
  93. * @return boolean True on success.
  94. *
  95. * @since 1.0
  96. */
  97. public function bind($data)
  98. {
  99. // Make sure there is a valid Form XML document.
  100. if (!($this->xml instanceof \SimpleXMLElement))
  101. {
  102. return false;
  103. }
  104. // The data must be an object or array.
  105. if (!is_object($data) && !is_array($data))
  106. {
  107. return false;
  108. }
  109. // Convert the object to an array.
  110. if ($data instanceof Registry)
  111. {
  112. $data = $data->toArray();
  113. }
  114. elseif (is_object($data))
  115. {
  116. $data = (array) $data;
  117. }
  118. // Process the input data.
  119. foreach ($data as $k => $v)
  120. {
  121. if ($this->findField($k))
  122. {
  123. // If the field exists set the value.
  124. $this->data->set($k, $v);
  125. }
  126. elseif (is_object($v) || ArrayHelper::isAssociative($v))
  127. {
  128. // If the value is an object or an associative array hand it off to the recursive bind level method.
  129. $this->bindLevel($k, $v);
  130. }
  131. }
  132. return true;
  133. }
  134. /**
  135. * Method to bind data to the form for the group level.
  136. *
  137. * @param string $group The dot-separated form group path on which to bind the data.
  138. * @param mixed $data An array or object of data to bind to the form for the group level.
  139. *
  140. * @return void
  141. *
  142. * @since 1.0
  143. */
  144. protected function bindLevel($group, $data)
  145. {
  146. // Ensure the input data is an array.
  147. settype($data, 'array');
  148. // Process the input data.
  149. foreach ($data as $k => $v)
  150. {
  151. if ($this->findField($k, $group))
  152. {
  153. // If the field exists set the value.
  154. $this->data->set($group . '.' . $k, $v);
  155. }
  156. elseif (is_object($v) || ArrayHelper::isAssociative($v))
  157. {
  158. // If the value is an object or an associative array, hand it off to the recursive bind level method
  159. $this->bindLevel($group . '.' . $k, $v);
  160. }
  161. }
  162. }
  163. /**
  164. * Method to filter the form data.
  165. *
  166. * @param array $data An array of field values to filter.
  167. * @param string $group The dot-separated form group path on which to filter the fields.
  168. *
  169. * @return mixed Array or false.
  170. *
  171. * @since 1.0
  172. */
  173. public function filter($data, $group = null)
  174. {
  175. // Make sure there is a valid Form XML document.
  176. if (!($this->xml instanceof \SimpleXMLElement))
  177. {
  178. return false;
  179. }
  180. $input = new Registry($data);
  181. $output = new Registry;
  182. // Get the fields for which to filter the data.
  183. $fields = $this->findFieldsByGroup($group);
  184. if (!$fields)
  185. {
  186. // PANIC!
  187. return false;
  188. }
  189. // Filter the fields.
  190. foreach ($fields as $field)
  191. {
  192. $name = (string) $field['name'];
  193. // Get the field groups for the element.
  194. $attrs = $field->xpath('ancestor::fields[@name]/@name');
  195. $groups = array_map('strval', $attrs ? $attrs : array());
  196. $group = implode('.', $groups);
  197. // Get the field value from the data input.
  198. if ($group)
  199. {
  200. // Filter the value if it exists.
  201. if ($input->exists($group . '.' . $name))
  202. {
  203. $output->set($group . '.' . $name, $this->filterField($field, $input->get($group . '.' . $name, (string) $field['default'])));
  204. }
  205. }
  206. else
  207. {
  208. // Filter the value if it exists.
  209. if ($input->exists($name))
  210. {
  211. $output->set($name, $this->filterField($field, $input->get($name, (string) $field['default'])));
  212. }
  213. }
  214. }
  215. return $output->toArray();
  216. }
  217. /**
  218. * Return all errors, if any.
  219. *
  220. * @return array Array of error messages or RuntimeException objects.
  221. *
  222. * @since 1.0
  223. */
  224. public function getErrors()
  225. {
  226. return $this->errors;
  227. }
  228. /**
  229. * Method to get a form field represented as a Field object.
  230. *
  231. * @param string $name The name of the form field.
  232. * @param string $group The optional dot-separated form group path on which to find the field.
  233. * @param mixed $value The optional value to use as the default for the field.
  234. *
  235. * @return mixed The Field object for the field or boolean false on error.
  236. *
  237. * @since 1.0
  238. */
  239. public function getField($name, $group = null, $value = null)
  240. {
  241. // Make sure there is a valid Form XML document.
  242. if (!($this->xml instanceof \SimpleXMLElement))
  243. {
  244. return false;
  245. }
  246. // Attempt to find the field by name and group.
  247. $element = $this->findField($name, $group);
  248. // If the field element was not found return false.
  249. if (!$element)
  250. {
  251. return false;
  252. }
  253. return $this->loadField($element, $group, $value);
  254. }
  255. /**
  256. * Method to get an attribute value from a field XML element. If the attribute doesn't exist or
  257. * is null then the optional default value will be used.
  258. *
  259. * @param string $name The name of the form field for which to get the attribute value.
  260. * @param string $attribute The name of the attribute for which to get a value.
  261. * @param mixed $default The optional default value to use if no attribute value exists.
  262. * @param string $group The optional dot-separated form group path on which to find the field.
  263. *
  264. * @return mixed The attribute value for the field.
  265. *
  266. * @since 1.0
  267. * @throws \UnexpectedValueException
  268. */
  269. public function getFieldAttribute($name, $attribute, $default = null, $group = null)
  270. {
  271. // Make sure there is a valid Form XML document.
  272. if (!($this->xml instanceof \SimpleXMLElement))
  273. {
  274. throw new \UnexpectedValueException(sprintf('%s::getFieldAttribute `xml` is not an instance of SimpleXMLElement', get_class($this)));
  275. }
  276. // Find the form field element from the definition.
  277. $element = $this->findField($name, $group);
  278. // If the element exists and the attribute exists for the field return the attribute value.
  279. if (($element instanceof \SimpleXMLElement) && ((string) $element[$attribute]))
  280. {
  281. return (string) $element[$attribute];
  282. }
  283. else
  284. // Otherwise return the given default value.
  285. {
  286. return $default;
  287. }
  288. }
  289. /**
  290. * Method to get an array of FormField objects in a given fieldset by name. If no name is
  291. * given then all fields are returned.
  292. *
  293. * @param string $set The optional name of the fieldset.
  294. *
  295. * @return array The array of FormField objects in the fieldset.
  296. *
  297. * @since 1.0
  298. */
  299. public function getFieldset($set = null)
  300. {
  301. $fields = array();
  302. // Get all of the field elements in the fieldset.
  303. if ($set)
  304. {
  305. $elements = $this->findFieldsByFieldset($set);
  306. }
  307. else
  308. // Get all fields.
  309. {
  310. $elements = $this->findFieldsByGroup();
  311. }
  312. // If no field elements were found return empty.
  313. if (empty($elements))
  314. {
  315. return $fields;
  316. }
  317. // Build the result array from the found field elements.
  318. foreach ($elements as $element)
  319. {
  320. // Get the field groups for the element.
  321. $attrs = $element->xpath('ancestor::fields[@name]/@name');
  322. $groups = array_map('strval', $attrs ? $attrs : array());
  323. $group = implode('.', $groups);
  324. // If the field is successfully loaded add it to the result array.
  325. if ($field = $this->loadField($element, $group))
  326. {
  327. $fields[$field->id] = $field;
  328. }
  329. }
  330. return $fields;
  331. }
  332. /**
  333. * Method to get an array of fieldset objects optionally filtered over a given field group.
  334. *
  335. * @param string $group The dot-separated form group path on which to filter the fieldsets.
  336. *
  337. * @return array The array of fieldset objects.
  338. *
  339. * @since 1.0
  340. */
  341. public function getFieldsets($group = null)
  342. {
  343. $fieldsets = array();
  344. $sets = array();
  345. // Make sure there is a valid Form XML document.
  346. if (!($this->xml instanceof \SimpleXMLElement))
  347. {
  348. return $fieldsets;
  349. }
  350. if ($group)
  351. {
  352. // Get the fields elements for a given group.
  353. $elements = &$this->findGroup($group);
  354. foreach ($elements as &$element)
  355. {
  356. // Get an array of <fieldset /> elements and fieldset attributes within the fields element.
  357. if ($tmp = $element->xpath('descendant::fieldset[@name] | descendant::field[@fieldset]/@fieldset'))
  358. {
  359. $sets = array_merge($sets, (array) $tmp);
  360. }
  361. }
  362. }
  363. else
  364. {
  365. // Get an array of <fieldset /> elements and fieldset attributes.
  366. $sets = $this->xml->xpath('//fieldset[@name] | //field[@fieldset]/@fieldset');
  367. }
  368. // If no fieldsets are found return empty.
  369. if (empty($sets))
  370. {
  371. return $fieldsets;
  372. }
  373. // Process each found fieldset.
  374. foreach ($sets as $set)
  375. {
  376. // Are we dealing with a fieldset element?
  377. if ((string) $set['name'])
  378. {
  379. // Only create it if it doesn't already exist.
  380. if (empty($fieldsets[(string) $set['name']]))
  381. {
  382. // Build the fieldset object.
  383. $fieldset = (object) array('name' => '', 'label' => '', 'description' => '');
  384. foreach ($set->attributes() as $name => $value)
  385. {
  386. $fieldset->$name = (string) $value;
  387. }
  388. // Add the fieldset object to the list.
  389. $fieldsets[$fieldset->name] = $fieldset;
  390. }
  391. }
  392. else
  393. // Must be dealing with a fieldset attribute.
  394. {
  395. // Only create it if it doesn't already exist.
  396. if (empty($fieldsets[(string) $set]))
  397. {
  398. // Attempt to get the fieldset element for data (throughout the entire form document).
  399. $tmp = $this->xml->xpath('//fieldset[@name="' . (string) $set . '"]');
  400. // If no element was found, build a very simple fieldset object.
  401. if (empty($tmp))
  402. {
  403. $fieldset = (object) array('name' => (string) $set, 'label' => '', 'description' => '');
  404. }
  405. else
  406. // Build the fieldset object from the element.
  407. {
  408. $fieldset = (object) array('name' => '', 'label' => '', 'description' => '');
  409. foreach ($tmp[0]->attributes() as $name => $value)
  410. {
  411. $fieldset->$name = (string) $value;
  412. }
  413. }
  414. // Add the fieldset object to the list.
  415. $fieldsets[$fieldset->name] = $fieldset;
  416. }
  417. }
  418. }
  419. return $fieldsets;
  420. }
  421. /**
  422. * Method to get the form control. This string serves as a container for all form fields. For
  423. * example, if there is a field named 'foo' and a field named 'bar' and the form control is
  424. * empty the fields will be rendered like: <input name="foo" /> and <input name="bar" />. If
  425. * the form control is set to 'joomla' however, the fields would be rendered like:
  426. * <input name="joomla[foo]" /> and <input name="joomla[bar]" />.
  427. *
  428. * @return string The form control string.
  429. *
  430. * @since 1.0
  431. */
  432. public function getFormControl()
  433. {
  434. return (string) $this->options['control'];
  435. }
  436. /**
  437. * Method to get an array of FormField objects in a given field group by name.
  438. *
  439. * @param string $group The dot-separated form group path for which to get the form fields.
  440. * @param boolean $nested True to also include fields in nested groups that are inside of the
  441. * group for which to find fields.
  442. *
  443. * @return array The array of FormField objects in the field group.
  444. *
  445. * @since 1.0
  446. */
  447. public function getGroup($group, $nested = false)
  448. {
  449. $fields = array();
  450. // Get all of the field elements in the field group.
  451. $elements = $this->findFieldsByGroup($group, $nested);
  452. // If no field elements were found return empty.
  453. if (empty($elements))
  454. {
  455. return $fields;
  456. }
  457. // Build the result array from the found field elements.
  458. foreach ($elements as $element)
  459. {
  460. // Get the field groups for the element.
  461. $attrs = $element->xpath('ancestor::fields[@name]/@name');
  462. $groups = array_map('strval', $attrs ? $attrs : array());
  463. $group = implode('.', $groups);
  464. // If the field is successfully loaded add it to the result array.
  465. if ($field = $this->loadField($element, $group))
  466. {
  467. $fields[$field->id] = $field;
  468. }
  469. }
  470. return $fields;
  471. }
  472. /**
  473. * Method to get a form field markup for the field input.
  474. *
  475. * @param string $name The name of the form field.
  476. * @param string $group The optional dot-separated form group path on which to find the field.
  477. * @param mixed $value The optional value to use as the default for the field.
  478. *
  479. * @return string The form field markup.
  480. *
  481. * @since 1.0
  482. */
  483. public function getInput($name, $group = null, $value = null)
  484. {
  485. // Attempt to get the form field.
  486. if ($field = $this->getField($name, $group, $value))
  487. {
  488. return $field->input;
  489. }
  490. return '';
  491. }
  492. /**
  493. * Method to get the label for a field input.
  494. *
  495. * @param string $name The name of the form field.
  496. * @param string $group The optional dot-separated form group path on which to find the field.
  497. *
  498. * @return string The form field label.
  499. *
  500. * @since 1.0
  501. */
  502. public function getLabel($name, $group = null)
  503. {
  504. // Attempt to get the form field.
  505. if ($field = $this->getField($name, $group))
  506. {
  507. return $field->label;
  508. }
  509. return '';
  510. }
  511. /**
  512. * Method to get the form name.
  513. *
  514. * @return string The name of the form.
  515. *
  516. * @since 1.0
  517. */
  518. public function getName()
  519. {
  520. return $this->name;
  521. }
  522. /**
  523. * Method to get the value of a field.
  524. *
  525. * @param string $name The name of the field for which to get the value.
  526. * @param string $group The optional dot-separated form group path on which to get the value.
  527. * @param mixed $default The optional default value of the field value is empty.
  528. *
  529. * @return mixed The value of the field or the default value if empty.
  530. *
  531. * @since 1.0
  532. */
  533. public function getValue($name, $group = null, $default = null)
  534. {
  535. // If a group is set use it.
  536. if ($group)
  537. {
  538. $return = $this->data->get($group . '.' . $name, $default);
  539. }
  540. else
  541. {
  542. $return = $this->data->get($name, $default);
  543. }
  544. return $return;
  545. }
  546. /**
  547. * Method to load the form description from an XML string or object.
  548. *
  549. * The replace option works per field. If a field being loaded already exists in the current
  550. * form definition then the behavior or load will vary depending upon the replace flag. If it
  551. * is set to true, then the existing field will be replaced in its exact location by the new
  552. * field being loaded. If it is false, then the new field being loaded will be ignored and the
  553. * method will move on to the next field to load.
  554. *
  555. * @param string $data The name of an XML string or object.
  556. * @param string $replace Flag to toggle whether form fields should be replaced if a field
  557. * already exists with the same group/name.
  558. * @param string $xpath An optional xpath to search for the fields.
  559. *
  560. * @return boolean True on success, false otherwise.
  561. *
  562. * @since 1.0
  563. */
  564. public function load($data, $replace = true, $xpath = false)
  565. {
  566. // If the data to load isn't already an XML element or string return false.
  567. if ((!($data instanceof \SimpleXMLElement)) && (!is_string($data)))
  568. {
  569. return false;
  570. }
  571. // Attempt to load the XML if a string.
  572. if (is_string($data))
  573. {
  574. try
  575. {
  576. $data = new \SimpleXMLElement($data);
  577. }
  578. catch (\Exception $e)
  579. {
  580. return false;
  581. }
  582. // Make sure the XML loaded correctly.
  583. if (!$data)
  584. {
  585. return false;
  586. }
  587. }
  588. // If we have no XML definition at this point let's make sure we get one.
  589. if (empty($this->xml))
  590. {
  591. // If no XPath query is set to search for fields, and we have a <form />, set it and return.
  592. if (!$xpath && ($data->getName() == 'form'))
  593. {
  594. $this->xml = $data;
  595. // Synchronize any paths found in the load.
  596. $this->syncPaths();
  597. return true;
  598. }
  599. else
  600. // Create a root element for the form.
  601. {
  602. $this->xml = new \SimpleXMLElement('<form></form>');
  603. }
  604. }
  605. // Get the XML elements to load.
  606. $elements = array();
  607. if ($xpath)
  608. {
  609. $elements = $data->xpath($xpath);
  610. }
  611. elseif ($data->getName() == 'form')
  612. {
  613. $elements = $data->children();
  614. }
  615. // If there is nothing to load return true.
  616. if (empty($elements))
  617. {
  618. return true;
  619. }
  620. // Load the found form elements.
  621. foreach ($elements as $element)
  622. {
  623. // Get an array of fields with the correct name.
  624. $fields = $element->xpath('descendant-or-self::field');
  625. foreach ($fields as $field)
  626. {
  627. // Get the group names as strings for ancestor fields elements.
  628. $attrs = $field->xpath('ancestor::fields[@name]/@name');
  629. $groups = array_map('strval', $attrs ? $attrs : array());
  630. // Check to see if the field exists in the current form.
  631. if ($current = $this->findField((string) $field['name'], implode('.', $groups)))
  632. {
  633. // If set to replace found fields, replace the data and remove the field so we don't add it twice.
  634. if ($replace)
  635. {
  636. $olddom = dom_import_simplexml($current);
  637. $loadeddom = dom_import_simplexml($field);
  638. $addeddom = $olddom->ownerDocument->importNode($loadeddom);
  639. $olddom->parentNode->replaceChild($addeddom, $olddom);
  640. $loadeddom->parentNode->removeChild($loadeddom);
  641. }
  642. else
  643. {
  644. unset($field);
  645. }
  646. }
  647. }
  648. // Merge the new field data into the existing XML document.
  649. self::addNode($this->xml, $element);
  650. }
  651. // Synchronize any paths found in the load.
  652. $this->syncPaths();
  653. return true;
  654. }
  655. /**
  656. * Method to load the form description from an XML file.
  657. *
  658. * The reset option works on a group basis. If the XML file references
  659. * groups that have already been created they will be replaced with the
  660. * fields in the new XML file unless the $reset parameter has been set
  661. * to false.
  662. *
  663. * @param string $file The filesystem path of an XML file.
  664. * @param string $reset Flag to toggle whether form fields should be replaced if a field
  665. * already exists with the same group/name.
  666. * @param string $xpath An optional xpath to search for the fields.
  667. *
  668. * @return boolean True on success, false otherwise.
  669. *
  670. * @since 1.0
  671. */
  672. public function loadFile($file, $reset = true, $xpath = false)
  673. {
  674. // Check to see if the path is an absolute path.
  675. if (!is_file($file))
  676. {
  677. // Not an absolute path so let's attempt to find one using JPath.
  678. $file = Path::find(FormHelper::addFormPath(), strtolower($file) . '.xml');
  679. // If unable to find the file return false.
  680. if (!$file)
  681. {
  682. return false;
  683. }
  684. }
  685. // Attempt to load the XML file.
  686. $xml = simplexml_load_file($file);
  687. return $this->load($xml, $reset, $xpath);
  688. }
  689. /**
  690. * Method to remove a field from the form definition.
  691. *
  692. * @param string $name The name of the form field for which remove.
  693. * @param string $group The optional dot-separated form group path on which to find the field.
  694. *
  695. * @return boolean True on success.
  696. *
  697. * @since 1.0
  698. * @throws \UnexpectedValueException
  699. */
  700. public function removeField($name, $group = null)
  701. {
  702. // Make sure there is a valid Form XML document.
  703. if (!($this->xml instanceof \SimpleXMLElement))
  704. {
  705. throw new \UnexpectedValueException(sprintf('%s::getFieldAttribute `xml` is not an instance of SimpleXMLElement', get_class($this)));
  706. }
  707. // Find the form field element from the definition.
  708. $element = $this->findField($name, $group);
  709. // If the element exists remove it from the form definition.
  710. if ($element instanceof \SimpleXMLElement)
  711. {
  712. $dom = dom_import_simplexml($element);
  713. $dom->parentNode->removeChild($dom);
  714. }
  715. return true;
  716. }
  717. /**
  718. * Method to remove a group from the form definition.
  719. *
  720. * @param string $group The dot-separated form group path for the group to remove.
  721. *
  722. * @return boolean True on success.
  723. *
  724. * @since 1.0
  725. * @throws \UnexpectedValueException
  726. */
  727. public function removeGroup($group)
  728. {
  729. // Make sure there is a valid Form XML document.
  730. if (!($this->xml instanceof \SimpleXMLElement))
  731. {
  732. throw new \UnexpectedValueException(sprintf('%s::getFieldAttribute `xml` is not an instance of SimpleXMLElement', get_class($this)));
  733. }
  734. // Get the fields elements for a given group.
  735. $elements = &$this->findGroup($group);
  736. foreach ($elements as &$element)
  737. {
  738. $dom = dom_import_simplexml($element);
  739. $dom->parentNode->removeChild($dom);
  740. }
  741. return true;
  742. }
  743. /**
  744. * Method to reset the form data store and optionally the form XML definition.
  745. *
  746. * @param boolean $xml True to also reset the XML form definition.
  747. *
  748. * @return boolean True on success.
  749. *
  750. * @since 1.0
  751. */
  752. public function reset($xml = false)
  753. {
  754. unset($this->data);
  755. $this->data = new Registry;
  756. if ($xml)
  757. {
  758. unset($this->xml);
  759. $this->xml = new \SimpleXMLElement('<form></form>');
  760. }
  761. return true;
  762. }
  763. /**
  764. * Method to set a field XML element to the form definition. If the replace flag is set then
  765. * the field will be set whether it already exists or not. If it isn't set, then the field
  766. * will not be replaced if it already exists.
  767. *
  768. * @param \SimpleXMLElement $element The XML element object representation of the form field.
  769. * @param string $group The optional dot-separated form group path on which to set the field.
  770. * @param boolean $replace True to replace an existing field if one already exists.
  771. *
  772. * @return boolean True on success.
  773. *
  774. * @since 1.0
  775. * @throws \UnexpectedValueException
  776. */
  777. public function setField(\SimpleXMLElement $element, $group = null, $replace = true)
  778. {
  779. // Make sure there is a valid Form XML document.
  780. if (!($this->xml instanceof \SimpleXMLElement))
  781. {
  782. throw new \UnexpectedValueException(sprintf('%s::getFieldAttribute `xml` is not an instance of SimpleXMLElement', get_class($this)));
  783. }
  784. // Find the form field element from the definition.
  785. $old = $this->findField((string) $element['name'], $group);
  786. // If an existing field is found and replace flag is false do nothing and return true.
  787. if (!$replace && !empty($old))
  788. {
  789. return true;
  790. }
  791. // If an existing field is found and replace flag is true remove the old field.
  792. if ($replace && !empty($old) && ($old instanceof \SimpleXMLElement))
  793. {
  794. $dom = dom_import_simplexml($old);
  795. $dom->parentNode->removeChild($dom);
  796. }
  797. // If no existing field is found find a group element and add the field as a child of it.
  798. if ($group)
  799. {
  800. // Get the fields elements for a given group.
  801. $fields = &$this->findGroup($group);
  802. // If an appropriate fields element was found for the group, add the element.
  803. if (isset($fields[0]) && ($fields[0] instanceof \SimpleXMLElement))
  804. {
  805. self::addNode($fields[0], $element);
  806. }
  807. }
  808. else
  809. {
  810. // Set the new field to the form.
  811. self::addNode($this->xml, $element);
  812. }
  813. // Synchronize any paths found in the load.
  814. $this->syncPaths();
  815. return true;
  816. }
  817. /**
  818. * Method to set an attribute value for a field XML element.
  819. *
  820. * @param string $name The name of the form field for which to set the attribute value.
  821. * @param string $attribute The name of the attribute for which to set a value.
  822. * @param mixed $value The value to set for the attribute.
  823. * @param string $group The optional dot-separated form group path on which to find the field.
  824. *
  825. * @return boolean True on success.
  826. *
  827. * @since 1.0
  828. * @throws \UnexpectedValueException
  829. */
  830. public function setFieldAttribute($name, $attribute, $value, $group = null)
  831. {
  832. // Make sure there is a valid Form XML document.
  833. if (!($this->xml instanceof \SimpleXMLElement))
  834. {
  835. throw new \UnexpectedValueException(sprintf('%s::getFieldAttribute `xml` is not an instance of SimpleXMLElement', get_class($this)));
  836. }
  837. // Find the form field element from the definition.
  838. $element = $this->findField($name, $group);
  839. // If the element doesn't exist return false.
  840. if (!($element instanceof \SimpleXMLElement))
  841. {
  842. return false;
  843. }
  844. else
  845. // Otherwise set the attribute and return true.
  846. {
  847. $element[$attribute] = $value;
  848. // Synchronize any paths found in the load.
  849. $this->syncPaths();
  850. return true;
  851. }
  852. }
  853. /**
  854. * Method to set some field XML elements to the form definition. If the replace flag is set then
  855. * the fields will be set whether they already exists or not. If it isn't set, then the fields
  856. * will not be replaced if they already exist.
  857. *
  858. * @param array &$elements The array of XML element object representations of the form fields.
  859. * @param string $group The optional dot-separated form group path on which to set the fields.
  860. * @param boolean $replace True to replace existing fields if they already exist.
  861. *
  862. * @return boolean True on success.
  863. *
  864. * @since 1.0
  865. * @throws \UnexpectedValueException
  866. */
  867. public function setFields(&$elements, $group = null, $replace = true)
  868. {
  869. // Make sure there is a valid Form XML document.
  870. if (!($this->xml instanceof \SimpleXMLElement))
  871. {
  872. throw new \UnexpectedValueException(sprintf('%s::getFieldAttribute `xml` is not an instance of SimpleXMLElement', get_class($this)));
  873. }
  874. // Make sure the elements to set are valid.
  875. foreach ($elements as $element)
  876. {
  877. if (!($element instanceof \SimpleXMLElement))
  878. {
  879. throw new \UnexpectedValueException(sprintf('$element not SimpleXMLElement in %s::setFields', get_class($this)));
  880. }
  881. }
  882. // Set the fields.
  883. $return = true;
  884. foreach ($elements as $element)
  885. {
  886. if (!$this->setField($element, $group, $replace))
  887. {
  888. $return = false;
  889. }
  890. }
  891. // Synchronize any paths found in the load.
  892. $this->syncPaths();
  893. return $return;
  894. }
  895. /**
  896. * Method to set the value of a field. If the field does not exist in the form then the method
  897. * will return false.
  898. *
  899. * @param string $name The name of the field for which to set the value.
  900. * @param string $group The optional dot-separated form group path on which to find the field.
  901. * @param mixed $value The value to set for the field.
  902. *
  903. * @return boolean True on success.
  904. *
  905. * @since 1.0
  906. */
  907. public function setValue($name, $group = null, $value = null)
  908. {
  909. // If the field does not exist return false.
  910. if (!$this->findField($name, $group))
  911. {
  912. return false;
  913. }
  914. // If a group is set use it.
  915. if ($group)
  916. {
  917. $this->data->set($group . '.' . $name, $value);
  918. }
  919. else
  920. {
  921. $this->data->set($name, $value);
  922. }
  923. return true;
  924. }
  925. /**
  926. * Method to validate form data.
  927. *
  928. * Validation warnings will be pushed into Form::errors and should be
  929. * retrieved with Form::getErrors() when validate returns boolean false.
  930. *
  931. * @param array $data An array of field values to validate.
  932. * @param string $group The optional dot-separated form group path on which to filter the
  933. * fields to be validated.
  934. *
  935. * @return mixed True on success.
  936. *
  937. * @since 1.0
  938. */
  939. public function validate($data, $group = null)
  940. {
  941. // Make sure there is a valid Form XML document.
  942. if (!($this->xml instanceof \SimpleXMLElement))
  943. {
  944. return false;
  945. }
  946. $return = true;
  947. // Create an input registry object from the data to validate.
  948. $input = new Registry($data);
  949. // Get the fields for which to validate the data.
  950. $fields = $this->findFieldsByGroup($group);
  951. if (!$fields)
  952. {
  953. // PANIC!
  954. return false;
  955. }
  956. // Validate the fields.
  957. foreach ($fields as $field)
  958. {
  959. $value = null;
  960. $name = (string) $field['name'];
  961. // Get the group names as strings for ancestor fields elements.
  962. $attrs = $field->xpath('ancestor::fields[@name]/@name');
  963. $groups = array_map('strval', $attrs ? $attrs : array());
  964. $group = implode('.', $groups);
  965. // Get the value from the input data.
  966. if ($group)
  967. {
  968. $value = $input->get($group . '.' . $name);
  969. }
  970. else
  971. {
  972. $value = $input->get($name);
  973. }
  974. // Validate the field.
  975. $valid = $this->validateField($field, $group, $value, $input);
  976. // Check for an error.
  977. if ($valid instanceof \Exception)
  978. {
  979. array_push($this->errors, $valid);
  980. $return = false;
  981. }
  982. }
  983. return $return;
  984. }
  985. /**
  986. * Method to apply an input filter to a value based on field data.
  987. *
  988. * @param string $element The XML element object representation of the form field.
  989. * @param mixed $value The value to filter for the field.
  990. *
  991. * @return mixed The filtered value.
  992. *
  993. * @since 1.0
  994. */
  995. protected function filterField($element, $value)
  996. {
  997. // Make sure there is a valid SimpleXMLElement.
  998. if (!($element instanceof \SimpleXMLElement))
  999. {
  1000. return false;
  1001. }
  1002. // Get the field filter type.
  1003. $filter = (string) $element['filter'];
  1004. // Process the input value based on the filter.
  1005. $return = null;
  1006. switch (strtoupper($filter))
  1007. {
  1008. // Do nothing, thus leaving the return value as null.
  1009. case 'UNSET':
  1010. break;
  1011. // No Filter.
  1012. case 'RAW':
  1013. $return = $value;
  1014. break;
  1015. // Filter the input as an array of integers.
  1016. case 'INT_ARRAY':
  1017. // Make sure the input is an array.
  1018. if (is_object($value))
  1019. {
  1020. $value = get_object_vars($value);
  1021. }
  1022. $value = is_array($value) ? $value : array($value);
  1023. $value = ArrayHelper::toInteger($value);
  1024. $return = $value;
  1025. break;
  1026. // Filter safe HTML.
  1027. case 'SAFEHTML':
  1028. $filterInput = new Filter\InputFilter(null, null, 1, 1);
  1029. $return = $filterInput->clean($value, 'string');
  1030. break;
  1031. // Ensures a protocol is present in the saved field. Only use when
  1032. // the only permitted protocols requre '://'. See Rule\Url for list of these.
  1033. case 'URL':
  1034. if (empty($value))
  1035. {
  1036. return false;
  1037. }
  1038. $filterInput = new Filter\InputFilter;
  1039. $value = $filterInput->clean($value, 'html');
  1040. $value = trim($value);
  1041. // Check for a protocol
  1042. $protocol = parse_url($value, PHP_URL_SCHEME);
  1043. // If there is no protocol and the relative option is not specified,
  1044. // we assume that it is an external URL and prepend http://.
  1045. if (($element['type'] == 'url' && !$protocol && !$element['relative'])
  1046. || (!$element['type'] == 'url' && !$protocol))
  1047. {
  1048. $protocol = 'http';
  1049. // If it looks like an internal link, then add the root.
  1050. if (substr($value, 0) == 'index.php')
  1051. {
  1052. $value = Uri::root() . $value;
  1053. }
  1054. // Otherwise we treat it is an external link.
  1055. // Put the url back together.
  1056. $value = $protocol . '://' . $value;
  1057. }
  1058. // If relative URLS are allowed we assume that URLs without protocols are internal.
  1059. elseif (!$protocol && $element['relative'])
  1060. {
  1061. $host = Uri::getInstance('SERVER')->gethost();
  1062. // If it starts with the host string, just prepend the protocol.
  1063. if (substr($value, 0) == $host)
  1064. {
  1065. $value = 'http://' . $value;
  1066. }
  1067. else
  1068. // Otherwise prepend the root.
  1069. {
  1070. $value = Uri::root() . $value;
  1071. }
  1072. }
  1073. $return = $value;
  1074. break;
  1075. case 'TEL':
  1076. $value = trim($value);
  1077. // Does it match the NANP pattern?
  1078. if (preg_match('/^(?:\+?1[-. ]?)?\(?([2-9][0-8][0-9])\)?[-. ]?([2-9][0-9]{2})[-. ]?([0-9]{4})$/', $value) == 1)
  1079. {
  1080. $number = (string) preg_replace('/[^\d]/', '', $value);
  1081. if (substr($number, 0, 1) == 1)
  1082. {
  1083. $number = substr($number, 1);
  1084. }
  1085. if (substr($number, 0, 2) == '+1')
  1086. {
  1087. $number = substr($number, 2);
  1088. }
  1089. $result = '1.' . $number;
  1090. }
  1091. elseif (preg_match('/^\+(?:[0-9] ?){6,14}[0-9]$/', $value) == 1)
  1092. // If not, does it match ITU-T?
  1093. {
  1094. $countrycode = substr($value, 0, strpos($value, ' '));
  1095. $countrycode = (string) preg_replace('/[^\d]/', '', $countrycode);
  1096. $number = strstr($value, ' ');
  1097. $number = (string) preg_replace('/[^\d]/', '', $number);
  1098. $result = $countrycode . '.' . $number;
  1099. }
  1100. elseif (preg_match('/^\+[0-9]{1,3}\.[0-9]{4,14}(?:x.+)?$/', $value) == 1)
  1101. // If not, does it match EPP?
  1102. {
  1103. if (strstr($value, 'x'))
  1104. {
  1105. $xpos = strpos($value, 'x');
  1106. $value = substr($value, 0, $xpos);
  1107. }
  1108. $result = str_replace('+', '', $value);
  1109. }
  1110. elseif (preg_match('/[0-9]{1,3}\.[0-9]{4,14}$/', $value) == 1)
  1111. // Maybe it is already ccc.nnnnnnn?
  1112. {
  1113. $result = $value;
  1114. }
  1115. else
  1116. // If not, can we make it a string of digits?
  1117. {
  1118. $value = (string) preg_replace('/[^\d]/', '', $value);
  1119. if ($value != null && strlen($value) <= 15)
  1120. {
  1121. $length = strlen($value);
  1122. // If it is fewer than 13 digits assume it is a local number
  1123. if ($length <= 12)
  1124. {
  1125. $result = '.' . $value;
  1126. }
  1127. else
  1128. {
  1129. // If it has 13 or more digits let's make a country code.
  1130. $cclen = $length - 12;
  1131. $result = substr($value, 0, $cclen) . '.' . substr($value, $cclen);
  1132. }
  1133. }
  1134. else
  1135. // If not let's not save anything.
  1136. {
  1137. $result = '';
  1138. }
  1139. }
  1140. $return = $result;
  1141. break;
  1142. default:
  1143. // Check for a callback filter.
  1144. if (strpos($filter, '::') !== false && is_callable(explode('::', $filter)))
  1145. {
  1146. $return = call_user_func(explode('::', $filter), $value);
  1147. }
  1148. elseif (function_exists($filter))
  1149. // Filter using a callback function if specified.
  1150. {
  1151. $return = call_user_func($filter, $value);
  1152. }
  1153. else
  1154. // Filter using InputFilter. All HTML code is filtered by default.
  1155. {
  1156. $filterInput = new Filter\InputFilter;
  1157. $return = $filterInput->clean($value, $filter);
  1158. }
  1159. break;
  1160. }
  1161. return $return;
  1162. }
  1163. /**
  1164. * Method to get a form field represented as an XML element object.
  1165. *
  1166. * @param string $name The name of the form field.
  1167. * @param string $group The optional dot-separated form group path on which to find the field.
  1168. *
  1169. * @return mixed The XML element object for the field or boolean false on error.
  1170. *
  1171. * @since 1.0
  1172. */
  1173. protected function findField($name, $group = null)
  1174. {
  1175. $element = false;
  1176. $fields = array();
  1177. // Make sure there is a valid Form XML document.
  1178. if (!($this->xml instanceof \SimpleXMLElement))
  1179. {
  1180. return false;
  1181. }
  1182. // Let's get the appropriate field element based on the method arguments.
  1183. if ($group)
  1184. {
  1185. // Get the fields elements for a given group.
  1186. $elements = &$this->findGroup($group);
  1187. // Get all of the field elements with the correct name for the fields elements.
  1188. foreach ($elements as $element)
  1189. {
  1190. // If there are matching field elements add them to the fields array.
  1191. if ($tmp = $element->xpath('descendant::field[@name="' . $name . '"]'))
  1192. {
  1193. $fields = array_merge($fields, $tmp);
  1194. }
  1195. }
  1196. // Make sure something was found.
  1197. if (!$fields)
  1198. {
  1199. return false;
  1200. }
  1201. // Use the first correct match in the given group.
  1202. $groupNames = explode('.', $group);
  1203. foreach ($fields as &$field)
  1204. {
  1205. // Get the group names as strings for ancestor fields elements.
  1206. $attrs = $field->xpath('ancestor::fields[@name]/@name');
  1207. $names = array_map('strval', $attrs ? $attrs : array());
  1208. // If the field is in the exact group use it and break out of the loop.
  1209. if ($names == (array) $groupNames)
  1210. {
  1211. $element = &$field;
  1212. break;
  1213. }
  1214. }
  1215. }
  1216. else
  1217. {
  1218. // Get an array of fields with the correct name.
  1219. $fields = $this->xml->xpath('//field[@name="' . $name . '"]');
  1220. // Make sure something was found.
  1221. if (!$fields)
  1222. {
  1223. return false;
  1224. }
  1225. // Search through the fields for the right one.
  1226. foreach ($fields as &$field)
  1227. {
  1228. // If we find an ancestor fields element with a group name then it isn't what we want.
  1229. if ($field->xpath('ancestor::fields[@name]'))
  1230. {
  1231. continue;
  1232. }
  1233. else
  1234. // Found it!
  1235. {
  1236. $element = &$field;
  1237. break;
  1238. }
  1239. }
  1240. }
  1241. return $element;
  1242. }
  1243. /**
  1244. * Method to get an array of <field /> elements from the form XML document which are
  1245. * in a specified fieldset by name.
  1246. *
  1247. * @param string $name The name of the fieldset.
  1248. *
  1249. * @return mixed Boolean false on error or array of SimpleXMLElement objects.
  1250. *
  1251. * @since 1.0
  1252. */
  1253. protected function &findFieldsByFieldset($name)
  1254. {
  1255. $false = false;
  1256. // Make sure there is a valid Form XML document.
  1257. if (!($this->xml instanceof \SimpleXMLElement))
  1258. {
  1259. return $false;
  1260. }
  1261. /*
  1262. * Get an array of <field /> elements that are underneath a <fieldset /> element
  1263. * with the appropriate name attribute (unless they are the decendents
  1264. * of another <field /> element for some reaon), and also any <field /> elements
  1265. * with the appropriate fieldset attribute.
  1266. */
  1267. $fields = $this->xml->xpath('//fieldset[@name="' . $name . '"]//field[not(ancestor::field)] | //field[@fieldset="' . $name . '"]');
  1268. return $fields;
  1269. }
  1270. /**
  1271. * Method to get an array of <field /> elements from the form XML document which are
  1272. * in a control group by name.
  1273. *
  1274. * @param mixed $group The optional dot-separated form group path on which to find the fields.
  1275. * Null will return all fields. False will return fields not in a group.
  1276. * @param boolean $nested True to also include fields in nested groups that are inside of the
  1277. * group for which to find fields.
  1278. *
  1279. * @return mixed Boolean false on error or array of SimpleXMLElement objects.
  1280. *
  1281. * @since 1.0
  1282. */
  1283. protected function &findFieldsByGroup($group = null, $nested = false)
  1284. {
  1285. $false = false;
  1286. $fields = array();
  1287. // Make sure there is a valid Form XML document.
  1288. if (!($this->xml instanceof \SimpleXMLElement))
  1289. {
  1290. return $false;
  1291. }
  1292. // Get only fields in a specific group?
  1293. if ($group)
  1294. {
  1295. // Get the fields elements for a given group.
  1296. $elements = &$this->findGroup($group);
  1297. // Get all of the field elements for the fields elements.
  1298. foreach ($elements as $element)
  1299. {
  1300. // If there are field elements add them to the return result.
  1301. if ($tmp = $element->xpath('descendant::field'))
  1302. {
  1303. // If we also want fields in nested groups then just merge the arrays.
  1304. if ($nested)
  1305. {
  1306. $fields = array_merge($fields, $tmp);
  1307. }
  1308. else
  1309. // If we want to exclude nested groups then we need to check each field.
  1310. {
  1311. $groupNames = explode('.', $group);
  1312. foreach ($tmp as $field)
  1313. {
  1314. // Get the names of the groups that the field is in.
  1315. $attrs = $field->xpath('ancestor::fields[@name]/@name');
  1316. $names = array_map('strval', $attrs ? $attrs : array());
  1317. // If the field is in the specific group then add it to the return list.
  1318. if ($names == (array) $groupNames)
  1319. {
  1320. $fields = array_merge($fields, array($field));
  1321. }
  1322. }
  1323. }
  1324. }
  1325. }
  1326. }
  1327. elseif ($group === false)
  1328. {
  1329. // Get only field elements not in a group.
  1330. $fields = $this->xml->xpath('descendant::fields[not(@name)]/field | descendant::fields[not(@name)]/fieldset/field ');
  1331. }
  1332. else
  1333. {
  1334. // Get an array of all the <field /> elements.
  1335. $fields = $this->xml->xpath('//field');
  1336. }
  1337. return $fields;
  1338. }
  1339. /**
  1340. * Method to get a form field group represented as an XML element object.
  1341. *
  1342. * @param string $group The dot-separated form group path on which to find the group.
  1343. *
  1344. * @return mixed An array of XML element objects for the group or boolean false on error.
  1345. *
  1346. * @since 1.0
  1347. */
  1348. protected function &findGroup($group)
  1349. {
  1350. $false = false;
  1351. $groups = array();
  1352. $tmp = array();
  1353. // Make sure there is a valid Form XML document.
  1354. if (!($this->xml instanceof \SimpleXMLElement))
  1355. {
  1356. return $false;
  1357. }
  1358. // Make sure there is actually a group to find.
  1359. $group = explode('.', $group);
  1360. if (!empty($group))
  1361. {
  1362. // Get any fields elements with the correct group name.
  1363. $elements = $this->xml->xpath('//fields[@name="' . (string) $group[0] . '"]');
  1364. // Check to make sure that there are no parent groups for each element.
  1365. foreach ($elements as $element)
  1366. {
  1367. if (!$element->xpath('ancestor::fields[@name]'))
  1368. {
  1369. $tmp[] = $element;
  1370. }
  1371. }
  1372. // Iterate through the nested groups to find any matching form field groups.
  1373. for ($i = 1, $n = count($group); $i < $n; $i++)
  1374. {
  1375. // Initialise some loop variables.
  1376. $validNames = array_slice($group, 0, $i + 1);
  1377. $current = $tmp;
  1378. $tmp = array();
  1379. // Check to make sure that there are no parent groups for each element.
  1380. foreach ($current as $element)
  1381. {
  1382. // Get any fields elements with the correct group name.
  1383. $children = $element->xpath('descendant::fields[@name="' . (string) $group[$i] . '"]');
  1384. // For the found fields elements validate that they are in the correct groups.
  1385. foreach ($children as $fields)
  1386. {
  1387. // Get the group names as strings for ancestor fields elements.
  1388. $attrs = $fields->xpath('ancestor-or-self::fields[@name]/@name');
  1389. $names = array_map('strval', $attrs ? $attrs : array());
  1390. // If the group names for the fields element match the valid names at this
  1391. // level add the fields element.
  1392. if ($validNames == $names)
  1393. {
  1394. $tmp[] = $fields;
  1395. }
  1396. }
  1397. }
  1398. }
  1399. // Only include valid XML objects.
  1400. foreach ($tmp as $element)
  1401. {
  1402. if ($element instanceof \SimpleXMLElement)
  1403. {
  1404. $groups[] = $element;
  1405. }
  1406. }
  1407. }
  1408. return $groups;
  1409. }
  1410. /**
  1411. * Method to load, setup and return a FormField object based on field data.
  1412. *
  1413. * @param string $element The XML element object representation of the form field.
  1414. * @param string $group The optional dot-separated form group path on which to find the field.
  1415. * @param mixed $value The optional value to use as the default for the field.
  1416. *
  1417. * @return mixed The FormField object for the field or boolean false on error.
  1418. *
  1419. * @since 1.0
  1420. */
  1421. protected function loadField($element, $group = null, $value = null)
  1422. {
  1423. // Make sure there is a valid SimpleXMLElement.
  1424. if (!($element instanceof \SimpleXMLElement))
  1425. {
  1426. return false;
  1427. }
  1428. // Get the field type.
  1429. $type = $element['type'] ? (string) $element['type'] : 'text';
  1430. // Load the Field object for the field.
  1431. $field = FormHelper::loadFieldType($type);
  1432. // If the object could not be loaded, get a text field object.
  1433. if ($field === false)
  1434. {
  1435. $field = FormHelper::loadFieldType('text');
  1436. }
  1437. /*
  1438. * Get the value for the form field if not set.
  1439. * Default to the translated version of the 'default' attribute
  1440. * if 'translate_default' attribute if set to 'true' or '1'
  1441. * else the value of the 'default' attribute for the field.
  1442. */
  1443. if ($value === null)
  1444. {
  1445. $default = (string) $element['default'];
  1446. if (($translate = $element['translate_default']) && ((string) $translate == 'true' || (string) $translate == '1'))
  1447. {
  1448. $lang = Language::getInstance();
  1449. if ($lang->hasKey($default))
  1450. {
  1451. $debug = $lang->setDebug(false);
  1452. $default = Text::_($default);
  1453. $lang->setDebug($debug);
  1454. }
  1455. else
  1456. {
  1457. $default = Text::_($default);
  1458. }
  1459. }
  1460. $value = $this->getValue((string) $element['name'], $group, $default);
  1461. }
  1462. // Setup the Field object.
  1463. $field->setForm($this);
  1464. if ($field->setup($element, $value, $group))
  1465. {
  1466. return $field;
  1467. }
  1468. else
  1469. {
  1470. return false;
  1471. }
  1472. }
  1473. /**
  1474. * Method to synchronize any field, form or rule paths contained in the XML document.
  1475. *
  1476. * @return boolean True on success.
  1477. *
  1478. * @since 1.0
  1479. * @todo Maybe we should receive all addXXXpaths attributes at once?
  1480. */
  1481. protected function syncPaths()
  1482. {
  1483. // Make sure there is a valid Form XML document.
  1484. if (!($this->xml instanceof \SimpleXMLElement))
  1485. {
  1486. return false;
  1487. }
  1488. // Get any addfieldpath attributes from the form definition.
  1489. $paths = $this->xml->xpath('//*[@addfieldpath]/@addfieldpath');
  1490. $paths = array_map('strval', $paths ? $paths : array());
  1491. // Add the field paths.
  1492. foreach ($paths as $path)
  1493. {
  1494. $path = JPATH_ROOT . '/' . ltrim($path, '/\\');
  1495. FormHelper::addFieldPath($path);
  1496. }
  1497. // Get any addformpath attributes from the form definition.
  1498. $paths = $this->xml->xpath('//*[@addformpath]/@addformpath');
  1499. $paths = array_map('strval', $paths ? $paths : array());
  1500. // Add the form paths.
  1501. foreach ($paths as $path)
  1502. {
  1503. $path = JPATH_ROOT . '/' . ltrim($path, '/\\');
  1504. FormHelper::addFormPath($path);
  1505. }
  1506. // Get any addrulepath attributes from the form definition.
  1507. $paths = $this->xml->xpath('//*[@addrulepath]/@addrulepath');
  1508. $paths = array_map('strval', $paths ? $paths : array());
  1509. // Add the rule paths.
  1510. foreach ($paths as $path)
  1511. {
  1512. $path = JPATH_ROOT . '/' . ltrim($path, '/\\');
  1513. FormHelper::addRulePath($path);
  1514. }
  1515. return true;
  1516. }
  1517. /**
  1518. * Method to validate a Field object based on field data.
  1519. *
  1520. * @param \SimpleXMLElement $element The XML element object representation of the form field.
  1521. * @param string $group The optional dot-separated form group path on which to find the field.
  1522. * @param mixed $value The optional value to use as the default for the field.
  1523. * @param Registry $input An optional Registry object with the entire data set to validate
  1524. * against the entire form.
  1525. *
  1526. * @return mixed Boolean true if field value is valid, Exception on failure.
  1527. *
  1528. * @since 1.0
  1529. * @throws \InvalidArgumentException
  1530. * @throws \UnexpectedValueException
  1531. */
  1532. protected function validateField(\SimpleXMLElement $element, $group = null, $value = null, Registry $input = null)
  1533. {
  1534. $valid = true;
  1535. // Check if the field is required.
  1536. $required = ((string) $element['required'] == 'true' || (string) $element['required'] == 'required');
  1537. if ($required)
  1538. {
  1539. // If the field is required and the value is empty return an error message.
  1540. if (($value === '') || ($value === null))
  1541. {
  1542. if ($element['label'])
  1543. {
  1544. $message = Text::_($element['label']);
  1545. }
  1546. else
  1547. {
  1548. $message = Text::_($element['name']);
  1549. }
  1550. $message = Text::sprintf('JLIB_FORM_VALIDATE_FIELD_REQUIRED', $message);
  1551. return new \RuntimeException($message);
  1552. }
  1553. }
  1554. // Get the field validation rule.
  1555. if ($type = (string) $element['validate'])
  1556. {
  1557. // Load the Rule object for the field.
  1558. $rule = FormHelper::loadRuleType($type);
  1559. // If the object could not be loaded return an error message.
  1560. if ($rule === false)
  1561. {
  1562. throw new \UnexpectedValueException(sprintf('%s::validateField() rule `%s` missing.', get_class($this), $type));
  1563. }
  1564. // Run the field validation rule test.
  1565. $valid = $rule->test($element, $value, $group, $input, $this);
  1566. // Check for an error in the validation test.
  1567. if ($valid instanceof \Exception)
  1568. {
  1569. return $valid;
  1570. }
  1571. }
  1572. // Check if the field is valid.
  1573. if ($valid === false)
  1574. {
  1575. // Does the field have a defined error message?
  1576. $message = (string) $element['message'];
  1577. if ($message)
  1578. {
  1579. $message = Text::_($element['message']);
  1580. return new \UnexpectedValueException($message);
  1581. }
  1582. else
  1583. {
  1584. $message = Text::_($element['label']);
  1585. $message = Text::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', $message);
  1586. return new \UnexpectedValueException($message);
  1587. }
  1588. }
  1589. return true;
  1590. }
  1591. /**
  1592. * Method to get an instance of a form.
  1593. *
  1594. * @param string $name The name of the form.
  1595. * @param string $data The name of an XML file or string to load as the form definition.
  1596. * @param array $options An array of form options.
  1597. * @param string $replace Flag to toggle whether form fields should be replaced if a field
  1598. * already exists with the same group/name.
  1599. * @param string $xpath An optional xpath to search for the fields.
  1600. *
  1601. * @return Form Instance of this class.
  1602. *
  1603. * @since 1.0
  1604. * @throws \InvalidArgumentException if no data provided.
  1605. * @throws \RuntimeException if the form could not be loaded.
  1606. */
  1607. public static function getInstance($name, $data = null, $options = array(), $replace = true, $xpath = false)
  1608. {
  1609. // Reference to array with form instances
  1610. $forms = &self::$forms;
  1611. // Only instantiate the form if it does not already exist.
  1612. if (!isset($forms[$name]))
  1613. {
  1614. $data = trim($data);
  1615. if (empty($data))
  1616. {
  1617. throw new \InvalidArgumentException(sprintf('%s(name, *%s*)', __METHOD__, gettype($data)));
  1618. }
  1619. // Instantiate the form.
  1620. $forms[$name] = new self($name, $options);
  1621. // Load t…

Large files files are truncated, but you can click here to view the full file