PageRenderTime 62ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/libraries/joomla/form/form.php

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