[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * Joomla! Content Management System 5 * 6 * @copyright (C) 2009 Open Source Matters, Inc. <https://www.joomla.org> 7 * @license GNU General Public License version 2 or later; see LICENSE.txt 8 */ 9 10 namespace Joomla\CMS\Form; 11 12 use Joomla\CMS\Factory; 13 use Joomla\CMS\Filesystem\Path; 14 use Joomla\CMS\Language\Text; 15 use Joomla\CMS\Object\CMSObject; 16 use Joomla\Database\DatabaseAwareInterface; 17 use Joomla\Database\DatabaseAwareTrait; 18 use Joomla\Database\DatabaseInterface; 19 use Joomla\Database\Exception\DatabaseNotFoundException; 20 use Joomla\Registry\Registry; 21 use Joomla\Utilities\ArrayHelper; 22 23 // phpcs:disable PSR1.Files.SideEffects 24 \defined('JPATH_PLATFORM') or die; 25 // phpcs:enable PSR1.Files.SideEffects 26 27 /** 28 * Form Class for the Joomla Platform. 29 * 30 * This class implements a robust API for constructing, populating, filtering, and validating forms. 31 * It uses XML definitions to construct form fields and a variety of field and rule classes to 32 * render and validate the form. 33 * 34 * @link https://www.w3.org/TR/html4/interact/forms.html 35 * @link https://html.spec.whatwg.org/multipage/forms.html 36 * @since 1.7.0 37 */ 38 class Form 39 { 40 use DatabaseAwareTrait; 41 42 /** 43 * The Registry data store for form fields during display. 44 * 45 * @var Registry 46 * @since 1.7.0 47 */ 48 protected $data; 49 50 /** 51 * The form object errors array. 52 * 53 * @var array 54 * @since 1.7.0 55 */ 56 protected $errors = []; 57 58 /** 59 * The name of the form instance. 60 * 61 * @var string 62 * @since 1.7.0 63 */ 64 protected $name; 65 66 /** 67 * The form object options for use in rendering and validation. 68 * 69 * @var array 70 * @since 1.7.0 71 */ 72 protected $options = []; 73 74 /** 75 * The form XML definition. 76 * 77 * @var \SimpleXMLElement 78 * @since 1.7.0 79 */ 80 protected $xml; 81 82 /** 83 * Form instances. 84 * 85 * @var Form[] 86 * @since 1.7.0 87 */ 88 protected static $forms = []; 89 90 /** 91 * Allows extensions to implement repeating elements 92 * 93 * @var boolean 94 * @since 3.2 95 */ 96 public $repeat = false; 97 98 /** 99 * Method to instantiate the form object. 100 * 101 * @param string $name The name of the form. 102 * @param array $options An array of form options. 103 * 104 * @since 1.7.0 105 */ 106 public function __construct($name, array $options = []) 107 { 108 // Set the name for the form. 109 $this->name = $name; 110 111 // Initialise the Registry data. 112 $this->data = new Registry(); 113 114 // Set the options if specified. 115 $this->options['control'] = $options['control'] ?? false; 116 } 117 118 /** 119 * Method to bind data to the form. 120 * 121 * @param mixed $data An array or object of data to bind to the form. 122 * 123 * @return boolean True on success. 124 * 125 * @since 1.7.0 126 */ 127 public function bind($data) 128 { 129 // Make sure there is a valid Form XML document. 130 if (!($this->xml instanceof \SimpleXMLElement)) { 131 throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__)); 132 } 133 134 // The data must be an object or array. 135 if (!\is_object($data) && !\is_array($data)) { 136 return false; 137 } 138 139 $this->bindLevel(null, $data); 140 141 return true; 142 } 143 144 /** 145 * Method to bind data to the form for the group level. 146 * 147 * @param string $group The dot-separated form group path on which to bind the data. 148 * @param mixed $data An array or object of data to bind to the form for the group level. 149 * 150 * @return void 151 * 152 * @since 1.7.0 153 */ 154 protected function bindLevel($group, $data) 155 { 156 // Ensure the input data is an array. 157 if (\is_object($data)) { 158 if ($data instanceof Registry) { 159 // Handle a Registry. 160 $data = $data->toArray(); 161 } elseif ($data instanceof CMSObject) { 162 // Handle a CMSObject. 163 $data = $data->getProperties(); 164 } else { 165 // Handle other types of objects. 166 $data = (array) $data; 167 } 168 } 169 170 // Process the input data. 171 foreach ($data as $k => $v) { 172 $level = $group ? $group . '.' . $k : $k; 173 174 if ($this->findField($k, $group)) { 175 // If the field exists set the value. 176 $this->data->set($level, $v); 177 } elseif (\is_object($v) || ArrayHelper::isAssociative($v)) { 178 // If the value is an object or an associative array, hand it off to the recursive bind level method. 179 $this->bindLevel($level, $v); 180 } 181 } 182 } 183 184 /** 185 * Return all errors, if any. 186 * 187 * @return array Array of error messages or RuntimeException objects. 188 * 189 * @since 1.7.0 190 */ 191 public function getErrors() 192 { 193 return $this->errors; 194 } 195 196 /** 197 * Method to get a form field represented as a FormField object. 198 * 199 * @param string $name The name of the form field. 200 * @param string $group The optional dot-separated form group path on which to find the field. 201 * @param mixed $value The optional value to use as the default for the field. 202 * 203 * @return FormField|boolean The FormField object for the field or boolean false on error. 204 * 205 * @since 1.7.0 206 */ 207 public function getField($name, $group = null, $value = null) 208 { 209 // Make sure there is a valid Form XML document. 210 if (!($this->xml instanceof \SimpleXMLElement)) { 211 throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__)); 212 } 213 214 // Attempt to find the field by name and group. 215 $element = $this->findField($name, $group); 216 217 // If the field element was not found return false. 218 if (!$element) { 219 return false; 220 } 221 222 return $this->loadField($element, $group, $value); 223 } 224 225 /** 226 * Method to get an attribute value from a field XML element. If the attribute doesn't exist or 227 * is null then the optional default value will be used. 228 * 229 * @param string $name The name of the form field for which to get the attribute value. 230 * @param string $attribute The name of the attribute for which to get a value. 231 * @param mixed $default The optional default value to use if no attribute value exists. 232 * @param string $group The optional dot-separated form group path on which to find the field. 233 * 234 * @return mixed The attribute value for the field. 235 * 236 * @since 1.7.0 237 * @throws \UnexpectedValueException 238 */ 239 public function getFieldAttribute($name, $attribute, $default = null, $group = null) 240 { 241 // Make sure there is a valid Form XML document. 242 if (!($this->xml instanceof \SimpleXMLElement)) { 243 throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__)); 244 } 245 246 // Find the form field element from the definition. 247 $element = $this->findField($name, $group); 248 249 // If the element exists and the attribute exists for the field return the attribute value. 250 if (($element instanceof \SimpleXMLElement) && \strlen((string) $element[$attribute])) { 251 return (string) $element[$attribute]; 252 } else { 253 // Otherwise return the given default value. 254 return $default; 255 } 256 } 257 258 /** 259 * Method to get an array of FormField objects in a given fieldset by name. If no name is 260 * given then all fields are returned. 261 * 262 * @param string $set The optional name of the fieldset. 263 * 264 * @return FormField[] The array of FormField objects in the fieldset. 265 * 266 * @since 1.7.0 267 */ 268 public function getFieldset($set = null) 269 { 270 $fields = []; 271 272 // Get all of the field elements in the fieldset. 273 if ($set) { 274 $elements = $this->findFieldsByFieldset($set); 275 } else { 276 // Get all fields. 277 $elements = $this->findFieldsByGroup(); 278 } 279 280 // If no field elements were found return empty. 281 if (empty($elements)) { 282 return $fields; 283 } 284 285 // Build the result array from the found field elements. 286 foreach ($elements as $element) { 287 // Get the field groups for the element. 288 $attrs = $element->xpath('ancestor::fields[@name]/@name'); 289 $groups = array_map('strval', $attrs ?: []); 290 $group = implode('.', $groups); 291 292 // If the field is successfully loaded add it to the result array. 293 if ($field = $this->loadField($element, $group)) { 294 $fields[$field->id] = $field; 295 } 296 } 297 298 return $fields; 299 } 300 301 /** 302 * Method to get an array of fieldset objects optionally filtered over a given field group. 303 * 304 * @param string $group The dot-separated form group path on which to filter the fieldsets. 305 * 306 * @return array The array of fieldset objects. 307 * 308 * @since 1.7.0 309 */ 310 public function getFieldsets($group = null) 311 { 312 $fieldsets = []; 313 $sets = []; 314 315 // Make sure there is a valid Form XML document. 316 if (!($this->xml instanceof \SimpleXMLElement)) { 317 throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__)); 318 } 319 320 if ($group) { 321 // Get the fields elements for a given group. 322 $elements = &$this->findGroup($group); 323 324 foreach ($elements as &$element) { 325 // Get an array of <fieldset /> elements and fieldset attributes within the fields element. 326 if ($tmp = $element->xpath('descendant::fieldset[@name] | descendant::field[@fieldset]/@fieldset')) { 327 $sets = array_merge($sets, (array) $tmp); 328 } 329 } 330 } else { 331 // Get an array of <fieldset /> elements and fieldset attributes. 332 $sets = $this->xml->xpath('//fieldset[@name and not(ancestor::field/form/*)] | //field[@fieldset and not(ancestor::field/form/*)]/@fieldset'); 333 } 334 335 // If no fieldsets are found return empty. 336 if (empty($sets)) { 337 return $fieldsets; 338 } 339 340 // Process each found fieldset. 341 foreach ($sets as $set) { 342 if ((string) $set['hidden'] === 'true') { 343 continue; 344 } 345 346 // Are we dealing with a fieldset element? 347 if ((string) $set['name']) { 348 // Only create it if it doesn't already exist. 349 if (empty($fieldsets[(string) $set['name']])) { 350 // Build the fieldset object. 351 $fieldset = (object) array('name' => '', 'label' => '', 'description' => ''); 352 353 foreach ($set->attributes() as $name => $value) { 354 $fieldset->$name = (string) $value; 355 } 356 357 // Add the fieldset object to the list. 358 $fieldsets[$fieldset->name] = $fieldset; 359 } 360 } else { 361 // Must be dealing with a fieldset attribute. 362 // Only create it if it doesn't already exist. 363 if (empty($fieldsets[(string) $set])) { 364 // Attempt to get the fieldset element for data (throughout the entire form document). 365 $tmp = $this->xml->xpath('//fieldset[@name="' . (string) $set . '"]'); 366 367 // If no element was found, build a very simple fieldset object. 368 if (empty($tmp)) { 369 $fieldset = (object) array('name' => (string) $set, 'label' => '', 'description' => ''); 370 } else { 371 // Build the fieldset object from the element. 372 $fieldset = (object) array('name' => '', 'label' => '', 'description' => ''); 373 374 foreach ($tmp[0]->attributes() as $name => $value) { 375 $fieldset->$name = (string) $value; 376 } 377 } 378 379 // Add the fieldset object to the list. 380 $fieldsets[$fieldset->name] = $fieldset; 381 } 382 } 383 } 384 385 return $fieldsets; 386 } 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 * 397 * @since 1.7.0 398 */ 399 public function getFormControl() 400 { 401 return (string) $this->options['control']; 402 } 403 404 /** 405 * Method to get an array of FormField objects in a given field group by name. 406 * 407 * @param string $group The dot-separated form group path for which to get the form fields. 408 * @param boolean $nested True to also include fields in nested groups that are inside of the 409 * group for which to find fields. 410 * 411 * @return FormField[] The array of FormField objects in the field group. 412 * 413 * @since 1.7.0 414 */ 415 public function getGroup($group, $nested = false) 416 { 417 $fields = []; 418 419 // Get all of the field elements in the field group. 420 $elements = $this->findFieldsByGroup($group, $nested); 421 422 // If no field elements were found return empty. 423 if (empty($elements)) { 424 return $fields; 425 } 426 427 // Build the result array from the found field elements. 428 foreach ($elements as $element) { 429 // Get the field groups for the element. 430 $attrs = $element->xpath('ancestor::fields[@name]/@name'); 431 $groups = array_map('strval', $attrs ?: []); 432 $group = implode('.', $groups); 433 434 // If the field is successfully loaded add it to the result array. 435 if ($field = $this->loadField($element, $group)) { 436 $fields[$field->id] = $field; 437 } 438 } 439 440 return $fields; 441 } 442 443 /** 444 * Method to get a form field markup for the field input. 445 * 446 * @param string $name The name of the form field. 447 * @param string $group The optional dot-separated form group path on which to find the field. 448 * @param mixed $value The optional value to use as the default for the field. 449 * 450 * @return string The form field markup. 451 * 452 * @since 1.7.0 453 */ 454 public function getInput($name, $group = null, $value = null) 455 { 456 // Attempt to get the form field. 457 if ($field = $this->getField($name, $group, $value)) { 458 return $field->input; 459 } 460 461 return ''; 462 } 463 464 /** 465 * Method to get the label for a field input. 466 * 467 * @param string $name The name of the form field. 468 * @param string $group The optional dot-separated form group path on which to find the field. 469 * 470 * @return string The form field label. 471 * 472 * @since 1.7.0 473 */ 474 public function getLabel($name, $group = null) 475 { 476 // Attempt to get the form field. 477 if ($field = $this->getField($name, $group)) { 478 return $field->label; 479 } 480 481 return ''; 482 } 483 484 /** 485 * Method to get the form name. 486 * 487 * @return string The name of the form. 488 * 489 * @since 1.7.0 490 */ 491 public function getName() 492 { 493 return $this->name; 494 } 495 496 /** 497 * Method to get the value of a field. 498 * 499 * @param string $name The name of the field for which to get the value. 500 * @param string $group The optional dot-separated form group path on which to get the value. 501 * @param mixed $default The optional default value of the field value is empty. 502 * 503 * @return mixed The value of the field or the default value if empty. 504 * 505 * @since 1.7.0 506 */ 507 public function getValue($name, $group = null, $default = null) 508 { 509 // If a group is set use it. 510 if ($group) { 511 $return = $this->data->get($group . '.' . $name, $default); 512 } else { 513 $return = $this->data->get($name, $default); 514 } 515 516 return $return; 517 } 518 519 /** 520 * Method to get a control group with label and input. 521 * 522 * @param string $name The name of the field for which to get the value. 523 * @param string $group The optional dot-separated form group path on which to get the value. 524 * @param mixed $default The optional default value of the field value is empty. 525 * @param array $options Any options to be passed into the rendering of the field 526 * 527 * @return string A string containing the html for the control group 528 * 529 * @since 3.2.3 530 */ 531 public function renderField($name, $group = null, $default = null, $options = []) 532 { 533 $field = $this->getField($name, $group, $default); 534 535 if ($field) { 536 return $field->renderField($options); 537 } 538 539 return ''; 540 } 541 542 /** 543 * Method to get all control groups with label and input of a fieldset. 544 * 545 * @param string $name The name of the fieldset for which to get the values. 546 * @param array $options Any options to be passed into the rendering of the field 547 * 548 * @return string A string containing the html for the control groups 549 * 550 * @since 3.2.3 551 */ 552 public function renderFieldset($name, $options = []) 553 { 554 $fields = $this->getFieldset($name); 555 $html = []; 556 557 foreach ($fields as $field) { 558 $html[] = $field->renderField($options); 559 } 560 561 return implode('', $html); 562 } 563 564 /** 565 * Method to load the form description from an XML string or object. 566 * 567 * The replace option works per field. If a field being loaded already exists in the current 568 * form definition then the behavior or load will vary depending upon the replace flag. If it 569 * is set to true, then the existing field will be replaced in its exact location by the new 570 * field being loaded. If it is false, then the new field being loaded will be ignored and the 571 * method will move on to the next field to load. 572 * 573 * @param string $data The name of an XML string or object. 574 * @param boolean $replace Flag to toggle whether form fields should be replaced if a field 575 * already exists with the same group/name. 576 * @param string $xpath An optional xpath to search for the fields. 577 * 578 * @return boolean True on success, false otherwise. 579 * 580 * @since 1.7.0 581 */ 582 public function load($data, $replace = true, $xpath = null) 583 { 584 // If the data to load isn't already an XML element or string return false. 585 if ((!($data instanceof \SimpleXMLElement)) && (!\is_string($data))) { 586 return false; 587 } 588 589 // Attempt to load the XML if a string. 590 if (\is_string($data)) { 591 try { 592 $data = new \SimpleXMLElement($data); 593 } catch (\Exception $e) { 594 return false; 595 } 596 } 597 598 // If we have no XML definition at this point let's make sure we get one. 599 if (empty($this->xml)) { 600 // If no XPath query is set to search for fields, and we have a <form />, set it and return. 601 if (!$xpath && ($data->getName() === 'form')) { 602 $this->xml = $data; 603 604 // Synchronize any paths found in the load. 605 $this->syncPaths(); 606 607 return true; 608 } else { 609 // Create a root element for the form. 610 $this->xml = new \SimpleXMLElement('<form></form>'); 611 } 612 } 613 614 // Get the XML elements to load. 615 $elements = []; 616 617 if ($xpath) { 618 $elements = $data->xpath($xpath); 619 } elseif ($data->getName() === 'form') { 620 $elements = $data->children(); 621 } 622 623 // If there is nothing to load return true. 624 if (empty($elements)) { 625 return true; 626 } 627 628 // Load the found form elements. 629 foreach ($elements as $element) { 630 // Get an array of fields with the correct name. 631 $fields = $element->xpath('descendant-or-self::field'); 632 633 foreach ($fields as $field) { 634 // Get the group names as strings for ancestor fields elements. 635 $attrs = $field->xpath('ancestor::fields[@name]/@name'); 636 $groups = array_map('strval', $attrs ?: []); 637 638 // Check to see if the field exists in the current form. 639 if ($current = $this->findField((string) $field['name'], implode('.', $groups))) { 640 // If set to replace found fields, replace the data and remove the field so we don't add it twice. 641 if ($replace) { 642 $olddom = dom_import_simplexml($current); 643 $loadeddom = dom_import_simplexml($field); 644 $addeddom = $olddom->ownerDocument->importNode($loadeddom, true); 645 $olddom->parentNode->replaceChild($addeddom, $olddom); 646 $loadeddom->parentNode->removeChild($loadeddom); 647 } else { 648 unset($field); 649 } 650 } 651 } 652 653 // Merge the new field data into the existing XML document. 654 self::addNode($this->xml, $element); 655 } 656 657 // Synchronize any paths found in the load. 658 $this->syncPaths(); 659 660 return true; 661 } 662 663 /** 664 * Method to load the form description from an XML file. 665 * 666 * The reset option works on a group basis. If the XML file references 667 * groups that have already been created they will be replaced with the 668 * fields in the new XML file unless the $reset parameter has been set 669 * to false. 670 * 671 * @param string $file The filesystem path of an XML file. 672 * @param boolean $reset Flag to toggle whether form fields should be replaced if a field 673 * already exists with the same group/name. 674 * @param string $xpath An optional xpath to search for the fields. 675 * 676 * @return boolean True on success, false otherwise. 677 * 678 * @since 1.7.0 679 */ 680 public function loadFile($file, $reset = true, $xpath = null) 681 { 682 // Check to see if the path is an absolute path. 683 if (!is_file($file)) { 684 // Not an absolute path so let's attempt to find one using JPath. 685 $file = Path::find(self::addFormPath(), strtolower($file) . '.xml'); 686 687 // If unable to find the file return false. 688 if (!$file) { 689 return false; 690 } 691 } 692 693 // Attempt to load the XML file. 694 $xml = simplexml_load_file($file); 695 696 return $this->load($xml, $reset, $xpath); 697 } 698 699 /** 700 * Method to remove a field from the form definition. 701 * 702 * @param string $name The name of the form field for which remove. 703 * @param string $group The optional dot-separated form group path on which to find the field. 704 * 705 * @return boolean True on success, false otherwise. 706 * 707 * @since 1.7.0 708 * @throws \UnexpectedValueException 709 */ 710 public function removeField($name, $group = null) 711 { 712 // Make sure there is a valid Form XML document. 713 if (!($this->xml instanceof \SimpleXMLElement)) { 714 throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__)); 715 } 716 717 // Find the form field element from the definition. 718 $element = $this->findField($name, $group); 719 720 // If the element exists remove it from the form definition. 721 if ($element instanceof \SimpleXMLElement) { 722 $dom = dom_import_simplexml($element); 723 $dom->parentNode->removeChild($dom); 724 725 return true; 726 } 727 728 return false; 729 } 730 731 /** 732 * Method to remove a group from the form definition. 733 * 734 * @param string $group The dot-separated form group path for the group to remove. 735 * 736 * @return boolean True on success. 737 * 738 * @since 1.7.0 739 * @throws \UnexpectedValueException 740 */ 741 public function removeGroup($group) 742 { 743 // Make sure there is a valid Form XML document. 744 if (!($this->xml instanceof \SimpleXMLElement)) { 745 throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__)); 746 } 747 748 // Get the fields elements for a given group. 749 $elements = &$this->findGroup($group); 750 751 foreach ($elements as &$element) { 752 $dom = dom_import_simplexml($element); 753 $dom->parentNode->removeChild($dom); 754 } 755 756 return true; 757 } 758 759 /** 760 * Method to reset the form data store and optionally the form XML definition. 761 * 762 * @param boolean $xml True to also reset the XML form definition. 763 * 764 * @return boolean True on success. 765 * 766 * @since 1.7.0 767 */ 768 public function reset($xml = false) 769 { 770 unset($this->data); 771 $this->data = new Registry(); 772 773 if ($xml) { 774 unset($this->xml); 775 $this->xml = new \SimpleXMLElement('<form></form>'); 776 } 777 778 return true; 779 } 780 781 /** 782 * Method to set a field XML element to the form definition. If the replace flag is set then 783 * the field will be set whether it already exists or not. If it isn't set, then the field 784 * will not be replaced if it already exists. 785 * 786 * @param \SimpleXMLElement $element The XML element object representation of the form field. 787 * @param string $group The optional dot-separated form group path on which to set the field. 788 * @param boolean $replace True to replace an existing field if one already exists. 789 * @param string $fieldset The name of the fieldset we are adding the field to. 790 * 791 * @return boolean True on success. 792 * 793 * @since 1.7.0 794 * @throws \UnexpectedValueException 795 */ 796 public function setField(\SimpleXMLElement $element, $group = null, $replace = true, $fieldset = 'default') 797 { 798 // Make sure there is a valid Form XML document. 799 if (!($this->xml instanceof \SimpleXMLElement)) { 800 throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__)); 801 } 802 803 // Find the form field element from the definition. 804 $old = $this->findField((string) $element['name'], $group); 805 806 // If an existing field is found and replace flag is false do nothing and return true. 807 if (!$replace && !empty($old)) { 808 return true; 809 } 810 811 // If an existing field is found and replace flag is true remove the old field. 812 if ($replace && !empty($old) && ($old instanceof \SimpleXMLElement)) { 813 $dom = dom_import_simplexml($old); 814 815 // Get the parent element, this should be the fieldset 816 $parent = $dom->parentNode; 817 $fieldset = $parent->getAttribute('name'); 818 819 $parent->removeChild($dom); 820 } 821 822 // Create the search path 823 $path = '//'; 824 825 if (!empty($group)) { 826 $path .= 'fields[@name="' . $group . '"]/'; 827 } 828 829 $path .= 'fieldset[@name="' . $fieldset . '"]'; 830 831 $fs = $this->xml->xpath($path); 832 833 if (isset($fs[0]) && ($fs[0] instanceof \SimpleXMLElement)) { 834 // Add field to the form. 835 self::addNode($fs[0], $element); 836 837 // Synchronize any paths found in the load. 838 $this->syncPaths(); 839 840 return true; 841 } 842 843 // We couldn't find a fieldset to add the field. Now we are checking, if we have set only a group 844 if (!empty($group)) { 845 $fields = &$this->findGroup($group); 846 847 // If an appropriate fields element was found for the group, add the element. 848 if (isset($fields[0]) && ($fields[0] instanceof \SimpleXMLElement)) { 849 self::addNode($fields[0], $element); 850 } 851 852 // Synchronize any paths found in the load. 853 $this->syncPaths(); 854 855 return true; 856 } 857 858 // We couldn't find a parent so we are adding it at root level 859 860 // Add field to the form. 861 self::addNode($this->xml, $element); 862 863 // Synchronize any paths found in the load. 864 $this->syncPaths(); 865 866 return true; 867 } 868 869 /** 870 * Method to set an attribute value for a field XML element. 871 * 872 * @param string $name The name of the form field for which to set the attribute value. 873 * @param string $attribute The name of the attribute for which to set a value. 874 * @param mixed $value The value to set for the attribute. 875 * @param string $group The optional dot-separated form group path on which to find the field. 876 * 877 * @return boolean True on success. 878 * 879 * @since 1.7.0 880 * @throws \UnexpectedValueException 881 */ 882 public function setFieldAttribute($name, $attribute, $value, $group = null) 883 { 884 // Make sure there is a valid Form XML document. 885 if (!($this->xml instanceof \SimpleXMLElement)) { 886 throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__)); 887 } 888 889 // Find the form field element from the definition. 890 $element = $this->findField($name, $group); 891 892 // If the element doesn't exist return false. 893 if (!($element instanceof \SimpleXMLElement)) { 894 return false; 895 } else { 896 // Otherwise set the attribute and return true. 897 $element[$attribute] = $value; 898 899 // Synchronize any paths found in the load. 900 $this->syncPaths(); 901 902 return true; 903 } 904 } 905 906 /** 907 * Method to set some field XML elements to the form definition. If the replace flag is set then 908 * the fields will be set whether they already exists or not. If it isn't set, then the fields 909 * will not be replaced if they already exist. 910 * 911 * @param array &$elements The array of XML element object representations of the form fields. 912 * @param string $group The optional dot-separated form group path on which to set the fields. 913 * @param boolean $replace True to replace existing fields if they already exist. 914 * @param string $fieldset The name of the fieldset we are adding the field to. 915 * 916 * @return boolean True on success. 917 * 918 * @since 1.7.0 919 * @throws \UnexpectedValueException 920 */ 921 public function setFields(&$elements, $group = null, $replace = true, $fieldset = 'default') 922 { 923 // Make sure there is a valid Form XML document. 924 if (!($this->xml instanceof \SimpleXMLElement)) { 925 throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__)); 926 } 927 928 // Make sure the elements to set are valid. 929 foreach ($elements as $element) { 930 if (!($element instanceof \SimpleXMLElement)) { 931 throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__)); 932 } 933 } 934 935 // Set the fields. 936 $return = true; 937 938 foreach ($elements as $element) { 939 if (!$this->setField($element, $group, $replace, $fieldset)) { 940 $return = false; 941 } 942 } 943 944 // Synchronize any paths found in the load. 945 $this->syncPaths(); 946 947 return $return; 948 } 949 950 /** 951 * Method to set the value of a field. If the field does not exist in the form then the method 952 * will return false. 953 * 954 * @param string $name The name of the field for which to set the value. 955 * @param string $group The optional dot-separated form group path on which to find the field. 956 * @param mixed $value The value to set for the field. 957 * 958 * @return boolean True on success. 959 * 960 * @since 1.7.0 961 */ 962 public function setValue($name, $group = null, $value = null) 963 { 964 // If the field does not exist return false. 965 if (!$this->findField($name, $group)) { 966 return false; 967 } 968 969 // If a group is set use it. 970 if ($group) { 971 $this->data->set($group . '.' . $name, $value); 972 } else { 973 $this->data->set($name, $value); 974 } 975 976 return true; 977 } 978 979 /** 980 * Method to process the form data. 981 * 982 * @param array $data An array of field values to filter. 983 * @param string $group The dot-separated form group path on which to filter the fields. 984 * 985 * @return mixed Array or false. 986 * 987 * @since 4.0.0 988 */ 989 public function process($data, $group = null) 990 { 991 $data = $this->filter($data, $group); 992 993 $valid = $this->validate($data, $group); 994 995 if (!$valid) { 996 return $valid; 997 } 998 999 return $this->postProcess($data, $group); 1000 } 1001 1002 /** 1003 * Method to filter the form data. 1004 * 1005 * @param array $data An array of field values to filter. 1006 * @param string $group The dot-separated form group path on which to filter the fields. 1007 * 1008 * @return mixed Array or false. 1009 * 1010 * @since 4.0.0 1011 */ 1012 public function filter($data, $group = null) 1013 { 1014 // Make sure there is a valid Form XML document. 1015 if (!($this->xml instanceof \SimpleXMLElement)) { 1016 throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__)); 1017 } 1018 1019 $input = new Registry($data); 1020 $output = new Registry(); 1021 1022 // Get the fields for which to filter the data. 1023 $fields = $this->findFieldsByGroup($group); 1024 1025 if (!$fields) { 1026 // PANIC! 1027 return false; 1028 } 1029 1030 // Filter the fields. 1031 foreach ($fields as $field) { 1032 $name = (string) $field['name']; 1033 1034 // Get the field groups for the element. 1035 $attrs = $field->xpath('ancestor::fields[@name]/@name'); 1036 $groups = array_map('strval', $attrs ?: []); 1037 $attrGroup = implode('.', $groups); 1038 1039 $key = $attrGroup ? $attrGroup . '.' . $name : $name; 1040 1041 // Filter the value if it exists. 1042 if ($input->exists($key)) { 1043 $fieldObj = $this->loadField($field, $group); 1044 1045 // Only set into the output if the field was supposed to render on the page (i.e. setup returned true) 1046 if ($fieldObj) { 1047 $output->set($key, $fieldObj->filter($input->get($key, (string) $field['default']), $group, $input)); 1048 } 1049 } 1050 } 1051 1052 return $output->toArray(); 1053 } 1054 1055 /** 1056 * Method to validate form data. 1057 * 1058 * Validation warnings will be pushed into JForm::errors and should be 1059 * retrieved with JForm::getErrors() when validate returns boolean false. 1060 * 1061 * @param array $data An array of field values to validate. 1062 * @param string $group The optional dot-separated form group path on which to filter the 1063 * fields to be validated. 1064 * 1065 * @return boolean True on success. 1066 * 1067 * @since 1.7.0 1068 */ 1069 public function validate($data, $group = null) 1070 { 1071 // Make sure there is a valid Form XML document. 1072 if (!($this->xml instanceof \SimpleXMLElement)) { 1073 throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__)); 1074 } 1075 1076 $return = true; 1077 1078 // Create an input registry object from the data to validate. 1079 $input = new Registry($data); 1080 1081 // Get the fields for which to validate the data. 1082 $fields = $this->findFieldsByGroup($group); 1083 1084 if (!$fields) { 1085 // PANIC! 1086 return false; 1087 } 1088 1089 // Validate the fields. 1090 foreach ($fields as $field) { 1091 $name = (string) $field['name']; 1092 1093 // Define field name for messages 1094 if ($field['label']) { 1095 $fieldLabel = $field['label']; 1096 1097 // Try to translate label if not set to false 1098 $translate = (string) $field['translateLabel']; 1099 1100 if (!($translate === 'false' || $translate === 'off' || $translate === '0')) { 1101 $fieldLabel = Text::_($fieldLabel); 1102 } 1103 } else { 1104 $fieldLabel = Text::_($name); 1105 } 1106 1107 $disabled = ((string) $field['disabled'] === 'true' || (string) $field['disabled'] === 'disabled'); 1108 1109 $fieldExistsInRequestData = $input->exists($name) || $input->exists($group . '.' . $name); 1110 1111 // If the field is disabled but it is passed in the request this is invalid as disabled fields are not added to the request 1112 if ($disabled && $fieldExistsInRequestData) { 1113 throw new \RuntimeException(Text::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', $fieldLabel)); 1114 } 1115 1116 // Get the field groups for the element. 1117 $attrs = $field->xpath('ancestor::fields[@name]/@name'); 1118 $groups = array_map('strval', $attrs ?: []); 1119 $attrGroup = implode('.', $groups); 1120 1121 $key = $attrGroup ? $attrGroup . '.' . $name : $name; 1122 1123 $fieldObj = $this->loadField($field, $attrGroup); 1124 1125 if ($fieldObj) { 1126 $valid = $fieldObj->validate($input->get($key), $attrGroup, $input); 1127 1128 // Check for an error. 1129 if ($valid instanceof \Exception) { 1130 $this->errors[] = $valid; 1131 $return = false; 1132 } 1133 } elseif (!$fieldObj && $input->exists($key)) { 1134 // The field returned false from setup and shouldn't be included in the page body - yet we received 1135 // a value for it. This is probably some sort of injection attack and should be rejected 1136 $this->errors[] = new \RuntimeException(Text::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', $key)); 1137 } 1138 } 1139 1140 return $return; 1141 } 1142 1143 /** 1144 * Method to post-process form data. 1145 * 1146 * @param array $data An array of field values to post-process. 1147 * @param string $group The optional dot-separated form group path on which to filter the 1148 * fields to be validated. 1149 * 1150 * @return mixed Array or false. 1151 * 1152 * @since 4.0.0 1153 */ 1154 public function postProcess($data, $group = null) 1155 { 1156 // Make sure there is a valid SimpleXMLElement 1157 if (!($this->xml instanceof \SimpleXMLElement)) { 1158 throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__)); 1159 } 1160 1161 $input = new Registry($data); 1162 $output = new Registry(); 1163 1164 // Get the fields for which to postProcess the data. 1165 $fields = $this->findFieldsByGroup($group); 1166 1167 if (!$fields) { 1168 // PANIC! 1169 return false; 1170 } 1171 1172 // Filter the fields. 1173 foreach ($fields as $field) { 1174 $name = (string) $field['name']; 1175 1176 // Get the field groups for the element. 1177 $attrs = $field->xpath('ancestor::fields[@name]/@name'); 1178 $groups = array_map('strval', $attrs ?: []); 1179 $attrGroup = implode('.', $groups); 1180 1181 $key = $attrGroup ? $attrGroup . '.' . $name : $name; 1182 1183 // Filter the value if it exists. 1184 if ($input->exists($key)) { 1185 $fieldobj = $this->loadField($field, $group); 1186 $output->set($key, $fieldobj->postProcess($input->get($key, (string) $field['default']), $group, $input)); 1187 } 1188 } 1189 1190 return $output->toArray(); 1191 } 1192 1193 /** 1194 * Method to get a form field represented as an XML element object. 1195 * 1196 * @param string $name The name of the form field. 1197 * @param string $group The optional dot-separated form group path on which to find the field. 1198 * 1199 * @return \SimpleXMLElement|boolean The XML element object for the field or boolean false on error. 1200 * 1201 * @since 1.7.0 1202 */ 1203 protected function findField($name, $group = null) 1204 { 1205 $element = false; 1206 $fields = []; 1207 1208 // Make sure there is a valid Form XML document. 1209 if (!($this->xml instanceof \SimpleXMLElement)) { 1210 throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__)); 1211 } 1212 1213 // Let's get the appropriate field element based on the method arguments. 1214 if ($group) { 1215 // Get the fields elements for a given group. 1216 $elements = &$this->findGroup($group); 1217 1218 // Get all of the field elements with the correct name for the fields elements. 1219 foreach ($elements as $el) { 1220 // If there are matching field elements add them to the fields array. 1221 if ($tmp = $el->xpath('descendant::field[@name="' . $name . '" and not(ancestor::field/form/*)]')) { 1222 $fields = array_merge($fields, $tmp); 1223 } 1224 } 1225 1226 // Make sure something was found. 1227 if (!$fields) { 1228 return false; 1229 } 1230 1231 // Use the first correct match in the given group. 1232 $groupNames = explode('.', $group); 1233 1234 foreach ($fields as &$field) { 1235 // Get the group names as strings for ancestor fields elements. 1236 $attrs = $field->xpath('ancestor::fields[@name]/@name'); 1237 $names = array_map('strval', $attrs ?: []); 1238 1239 // If the field is in the exact group use it and break out of the loop. 1240 if ($names == (array) $groupNames) { 1241 $element = &$field; 1242 break; 1243 } 1244 } 1245 } else { 1246 // Get an array of fields with the correct name. 1247 $fields = $this->xml->xpath('//field[@name="' . $name . '" and not(ancestor::field/form/*)]'); 1248 1249 // Make sure something was found. 1250 if (!$fields) { 1251 return false; 1252 } 1253 1254 // Search through the fields for the right one. 1255 foreach ($fields as &$field) { 1256 // If we find an ancestor fields element with a group name then it isn't what we want. 1257 if ($field->xpath('ancestor::fields[@name]')) { 1258 continue; 1259 } else { 1260 // Found it! 1261 $element = &$field; 1262 break; 1263 } 1264 } 1265 } 1266 1267 return $element; 1268 } 1269 1270 /** 1271 * Method to get an array of `<field>` elements from the form XML document which are in a specified fieldset by name. 1272 * 1273 * @param string $name The name of the fieldset. 1274 * 1275 * @return \SimpleXMLElement[]|boolean Boolean false on error or array of SimpleXMLElement objects. 1276 * 1277 * @since 1.7.0 1278 */ 1279 protected function &findFieldsByFieldset($name) 1280 { 1281 // Make sure there is a valid Form XML document. 1282 if (!($this->xml instanceof \SimpleXMLElement)) { 1283 throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__)); 1284 } 1285 1286 /* 1287 * Get an array of <field /> elements that are underneath a <fieldset /> element 1288 * with the appropriate name attribute, and also any <field /> elements with 1289 * the appropriate fieldset attribute. To allow repeatable elements only fields 1290 * which are not descendants of other fields are selected. 1291 */ 1292 $fields = $this->xml->xpath('(//fieldset[@name="' . $name . '"]//field | //field[@fieldset="' . $name . '"])[not(ancestor::field)]'); 1293 1294 return $fields; 1295 } 1296 1297 /** 1298 * Method to get an array of `<field>` elements from the form XML document which are in a control group by name. 1299 * 1300 * @param mixed $group The optional dot-separated form group path on which to find the fields. 1301 * Null will return all fields. False will return fields not in a group. 1302 * @param boolean $nested True to also include fields in nested groups that are inside of the 1303 * group for which to find fields. 1304 * 1305 * @return \SimpleXMLElement[]|boolean Boolean false on error or array of SimpleXMLElement objects. 1306 * 1307 * @since 1.7.0 1308 */ 1309 protected function &findFieldsByGroup($group = null, $nested = false) 1310 { 1311 $fields = []; 1312 1313 // Make sure there is a valid Form XML document. 1314 if (!($this->xml instanceof \SimpleXMLElement)) { 1315 throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__)); 1316 } 1317 1318 // Get only fields in a specific group? 1319 if ($group) { 1320 // Get the fields elements for a given group. 1321 $elements = &$this->findGroup($group); 1322 1323 // Get all of the field elements for the fields elements. 1324 foreach ($elements as $element) { 1325 // If there are field elements add them to the return result. 1326 if ($tmp = $element->xpath('descendant::field')) { 1327 // If we also want fields in nested groups then just merge the arrays. 1328 if ($nested) { 1329 $fields = array_merge($fields, $tmp); 1330 } else { 1331 // If we want to exclude nested groups then we need to check each field. 1332 $groupNames = explode('.', $group); 1333 1334 foreach ($tmp as $field) { 1335 // Get the names of the groups that the field is in. 1336 $attrs = $field->xpath('ancestor::fields[@name]/@name'); 1337 $names = array_map('strval', $attrs ?: []); 1338 1339 // If the field is in the specific group then add it to the return list. 1340 if ($names == (array) $groupNames) { 1341 $fields = array_merge($fields, array($field)); 1342 } 1343 } 1344 } 1345 } 1346 } 1347 } elseif ($group === false) { 1348 // Get only field elements not in a group. 1349 $fields = $this->xml->xpath('descendant::fields[not(@name)]/field | descendant::fields[not(@name)]/fieldset/field '); 1350 } else { 1351 // Get an array of all the <field /> elements. 1352 $fields = $this->xml->xpath('//field[not(ancestor::field/form/*)]'); 1353 } 1354 1355 return $fields; 1356 } 1357 1358 /** 1359 * Method to get a form field group represented as an XML element object. 1360 * 1361 * @param string $group The dot-separated form group path on which to find the group. 1362 * 1363 * @return \SimpleXMLElement[]|boolean An array of XML element objects for the group or boolean false on error. 1364 * 1365 * @since 1.7.0 1366 */ 1367 protected function &findGroup($group) 1368 { 1369 $groups = []; 1370 $tmp = []; 1371 1372 // Make sure there is a valid Form XML document. 1373 if (!($this->xml instanceof \SimpleXMLElement)) { 1374 throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__)); 1375 } 1376 1377 // Make sure there is actually a group to find. 1378 $group = explode('.', $group); 1379 1380 if (!empty($group)) { 1381 // Get any fields elements with the correct group name. 1382 $elements = $this->xml->xpath('//fields[@name="' . (string) $group[0] . '" and not(ancestor::field/form/*)]'); 1383 1384 // Check to make sure that there are no parent groups for each element. 1385 foreach ($elements as $element) { 1386 if (!$element->xpath('ancestor::fields[@name]')) { 1387 $tmp[] = $element; 1388 } 1389 } 1390 1391 // Iterate through the nested groups to find any matching form field groups. 1392 for ($i = 1, $n = \count($group); $i < $n; $i++) { 1393 // Initialise some loop variables. 1394 $validNames = \array_slice($group, 0, $i + 1); 1395 $current = $tmp; 1396 $tmp = []; 1397 1398 // Check to make sure that there are no parent groups for each element. 1399 foreach ($current as $element) { 1400 // Get any fields elements with the correct group name. 1401 $children = $element->xpath('descendant::fields[@name="' . (string) $group[$i] . '"]'); 1402 1403 // For the found fields elements validate that they are in the correct groups. 1404 foreach ($children as $fields) { 1405 // Get the group names as strings for ancestor fields elements. 1406 $attrs = $fields->xpath('ancestor-or-self::fields[@name]/@name'); 1407 $names = array_map('strval', $attrs ?: []); 1408 1409 // If the group names for the fields element match the valid names at this 1410 // level add the fields element. 1411 if ($validNames == $names) { 1412 $tmp[] = $fields; 1413 } 1414 } 1415 } 1416 } 1417 1418 // Only include valid XML objects. 1419 foreach ($tmp as $element) { 1420 if ($element instanceof \SimpleXMLElement) { 1421 $groups[] = $element; 1422 } 1423 } 1424 } 1425 1426 return $groups; 1427 } 1428 1429 /** 1430 * Method to load, setup and return a FormField object based on field data. 1431 * 1432 * @param string $element The XML element object representation of the form field. 1433 * @param string $group The optional dot-separated form group path on which to find the field. 1434 * @param mixed $value The optional value to use as the default for the field. 1435 * 1436 * @return FormField|boolean The FormField object for the field or boolean false on error. 1437 * 1438 * @since 1.7.0 1439 */ 1440 protected function loadField($element, $group = null, $value = null) 1441 { 1442 // Make sure there is a valid SimpleXMLElement. 1443 if (!($element instanceof \SimpleXMLElement)) { 1444 throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__)); 1445 } 1446 1447 // Get the field type. 1448 $type = $element['type'] ? (string) $element['type'] : 'text'; 1449 1450 // Load the FormField object for the field. 1451 $field = FormHelper::loadFieldType($type); 1452 1453 if ($field instanceof DatabaseAwareInterface) { 1454 try { 1455 $field->setDatabase($this->getDatabase()); 1456 } catch (DatabaseNotFoundException $e) { 1457 @trigger_error(sprintf('Database must be set, this will not be caught anymore in 5.0.'), E_USER_DEPRECATED); 1458 $field->setDatabase(Factory::getContainer()->get(DatabaseInterface::class)); 1459 } 1460 } 1461 1462 // If the object could not be loaded, get a text field object. 1463 if ($field === false) { 1464 $field = FormHelper::loadFieldType('text'); 1465 } 1466 1467 /* 1468 * Get the value for the form field if not set. 1469 * Default to the translated version of the 'default' attribute 1470 * if 'translate_default' attribute if set to 'true' or '1' 1471 * else the value of the 'default' attribute for the field. 1472 */ 1473 if ($value === null) { 1474 $default = (string) ($element['default'] ? $element['default'] : $element->default); 1475 1476 if (($translate = $element['translate_default']) && ((string) $translate === 'true' || (string) $translate === '1')) { 1477 $lang = Factory::getLanguage(); 1478 1479 if ($lang->hasKey($default)) { 1480 $debug = $lang->setDebug(false); 1481 $default = Text::_($default); 1482 $lang->setDebug($debug); 1483 } else { 1484 $default = Text::_($default); 1485 } 1486 } 1487 1488 $value = $this->getValue((string) $element['name'], $group, $default); 1489 } 1490 1491 // Setup the FormField object. 1492 $field->setForm($this); 1493 1494 if ($field->setup($element, $value, $group)) { 1495 return $field; 1496 } else { 1497 return false; 1498 } 1499 } 1500 1501 /** 1502 * Method to synchronize any field, form or rule paths contained in the XML document. 1503 * 1504 * @return boolean True on success. 1505 * 1506 * @since 1.7.0 1507 * @todo Maybe we should receive all addXXXpaths attributes at once? 1508 */ 1509 protected function syncPaths() 1510 { 1511 // Make sure there is a valid Form XML document. 1512 if (!($this->xml instanceof \SimpleXMLElement)) { 1513 throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__)); 1514 } 1515 1516 // Get any addfieldpath attributes from the form definition. 1517 $paths = $this->xml->xpath('//*[@addfieldpath]/@addfieldpath'); 1518 $paths = array_map('strval', $paths ?: []); 1519 1520 // Add the field paths. 1521 foreach ($paths as $path) { 1522 $path = JPATH_ROOT . '/' . ltrim($path, '/\\'); 1523 self::addFieldPath($path); 1524 } 1525 1526 // Get any addformpath attributes from the form definition. 1527 $paths = $this->xml->xpath('//*[@addformpath]/@addformpath'); 1528 $paths = array_map('strval', $paths ?: []); 1529 1530 // Add the form paths. 1531 foreach ($paths as $path) { 1532 $path = JPATH_ROOT . '/' . ltrim($path, '/\\'); 1533 self::addFormPath($path); 1534 } 1535 1536 // Get any addrulepath attributes from the form definition. 1537 $paths = $this->xml->xpath('//*[@addrulepath]/@addrulepath'); 1538 $paths = array_map('strval', $paths ?: []); 1539 1540 // Add the rule paths. 1541 foreach ($paths as $path) { 1542 $path = JPATH_ROOT . '/' . ltrim($path, '/\\'); 1543 self::addRulePath($path); 1544 } 1545 1546 // Get any addrulepath attributes from the form definition. 1547 $paths = $this->xml->xpath('//*[@addfilterpath]/@addfilterpath'); 1548 $paths = array_map('strval', $paths ?: []); 1549 1550 // Add the rule paths. 1551 foreach ($paths as $path) { 1552 $path = JPATH_ROOT . '/' . ltrim($path, '/\\'); 1553 self::addFilterPath($path); 1554 } 1555 1556 // Get any addfieldprefix attributes from the form definition. 1557 $prefixes = $this->xml->xpath('//*[@addfieldprefix]/@addfieldprefix'); 1558 $prefixes = array_map('strval', $prefixes ?: []); 1559 1560 // Add the field prefixes. 1561 foreach ($prefixes as $prefix) { 1562 FormHelper::addFieldPrefix($prefix); 1563 } 1564 1565 // Get any addformprefix attributes from the form definition. 1566 $prefixes = $this->xml->xpath('//*[@addformprefix]/@addformprefix'); 1567 $prefixes = array_map('strval', $prefixes ?: []); 1568 1569 // Add the field prefixes. 1570 foreach ($prefixes as $prefix) { 1571 FormHelper::addFormPrefix($prefix); 1572 } 1573 1574 // Get any addruleprefix attributes from the form definition. 1575 $prefixes = $this->xml->xpath('//*[@addruleprefix]/@addruleprefix'); 1576 $prefixes = array_map('strval', $prefixes ?: []); 1577 1578 // Add the field prefixes. 1579 foreach ($prefixes as $prefix) { 1580 FormHelper::addRulePrefix($prefix); 1581 } 1582 1583 // Get any addruleprefix attributes from the form definition. 1584 $prefixes = $this->xml->xpath('//*[@addfilterprefix]/@addfilterprefix'); 1585 $prefixes = array_map('strval', $prefixes ?: []); 1586 1587 // Add the field prefixes. 1588 foreach ($prefixes as $prefix) { 1589 FormHelper::addFilterPrefix($prefix); 1590 } 1591 1592 return true; 1593 } 1594 1595 /** 1596 * Proxy for {@link FormHelper::addFieldPath()}. 1597 * 1598 * @param mixed $new A path or array of paths to add. 1599 * 1600 * @return array The list of paths that have been added. 1601 * 1602 * @since 1.7.0 1603 */ 1604 public static function addFieldPath($new = null) 1605 { 1606 return FormHelper::addFieldPath($new); 1607 } 1608 1609 /** 1610 * Proxy for FormHelper::addFormPath(). 1611 * 1612 * @param mixed $new A path or array of paths to add. 1613 * 1614 * @return array The list of paths that have been added. 1615 * 1616 * @see FormHelper::addFormPath() 1617 * @since 1.7.0 1618 */ 1619 public static function addFormPath($new = null) 1620 { 1621 return FormHelper::addFormPath($new); 1622 } 1623 1624 /** 1625 * Proxy for FormHelper::addRulePath(). 1626 * 1627 * @param mixed $new A path or array of paths to add. 1628 * 1629 * @return array The list of paths that have been added. 1630 * 1631 * @see FormHelper::addRulePath() 1632 * @since 1.7.0 1633 */ 1634 public static function addRulePath($new = null) 1635 { 1636 return FormHelper::addRulePath($new); 1637 } 1638 1639 /** 1640 * Proxy for FormHelper::addFilterPath(). 1641 * 1642 * @param mixed $new A path or array of paths to add. 1643 * 1644 * @return array The list of paths that have been added. 1645 * 1646 * @see FormHelper::addFilterPath() 1647 * @since 4.0.0 1648 */ 1649 public static function addFilterPath($new = null) 1650 { 1651 return FormHelper::addFilterPath($new); 1652 } 1653 1654 /** 1655 * Method to get an instance of a form. 1656 * 1657 * @param string $name The name of the form. 1658 * @param string $data The name of an XML file or string to load as the form definition. 1659 * @param array $options An array of form options. 1660 * @param boolean $replace Flag to toggle whether form fields should be replaced if a field 1661 * already exists with the same group/name. 1662 * @param string|boolean $xpath An optional xpath to search for the fields. 1663 * 1664 * @return Form Form instance. 1665 * 1666 * @since 1.7.0 1667 * @deprecated 5.0 Use the FormFactory service from the container 1668 * @throws \InvalidArgumentException if no data provided. 1669 * @throws \RuntimeException if the form could not be loaded. 1670 */ 1671 public static function getInstance($name, $data = null, $options = [], $replace = true, $xpath = false) 1672 { 1673 // Reference to array with form instances 1674 $forms = &self::$forms; 1675 1676 // Only instantiate the form if it does not already exist. 1677 if (!isset($forms[$name])) { 1678 $data = trim($data); 1679 1680 if (empty($data)) { 1681 throw new \InvalidArgumentException(sprintf('%1$s(%2$s, *%3$s*)', __METHOD__, $name, \gettype($data))); 1682 } 1683 1684 // Instantiate the form. 1685 $forms[$name] = Factory::getContainer()->get(FormFactoryInterface::class)->createForm($name, $options); 1686 1687 // Load the data. 1688 if (substr($data, 0, 1) === '<') { 1689 if ($forms[$name]->load($data, $replace, $xpath) == false) { 1690 throw new \RuntimeException(sprintf('%s() could not load form', __METHOD__)); 1691 } 1692 } else { 1693 if ($forms[$name]->loadFile($data, $replace, $xpath) == false) { 1694 throw new \RuntimeException(sprintf('%s() could not load file', __METHOD__)); 1695 } 1696 } 1697 } 1698 1699 return $forms[$name]; 1700 } 1701 1702 /** 1703 * Adds a new child SimpleXMLElement node to the source. 1704 * 1705 * @param \SimpleXMLElement $source The source element on which to append. 1706 * @param \SimpleXMLElement $new The new element to append. 1707 * 1708 * @return void 1709 * 1710 * @since 1.7.0 1711 */ 1712 protected static function addNode(\SimpleXMLElement $source, \SimpleXMLElement $new) 1713 { 1714 // Add the new child node. 1715 $node = $source->addChild($new->getName(), htmlspecialchars(trim($new))); 1716 1717 // Add the attributes of the child node. 1718 foreach ($new->attributes() as $name => $value) { 1719 $node->addAttribute($name, $value); 1720 } 1721 1722 // Add any children of the new node. 1723 foreach ($new->children() as $child) { 1724 self::addNode($node, $child); 1725 } 1726 } 1727 1728 /** 1729 * Update the attributes of a child node 1730 * 1731 * @param \SimpleXMLElement $source The source element on which to append the attributes 1732 * @param \SimpleXMLElement $new The new element to append 1733 * 1734 * @return void 1735 * 1736 * @since 1.7.0 1737 */ 1738 protected static function mergeNode(\SimpleXMLElement $source, \SimpleXMLElement $new) 1739 { 1740 // Update the attributes of the child node. 1741 foreach ($new->attributes() as $name => $value) { 1742 if (isset($source[$name])) { 1743 $source[$name] = (string) $value; 1744 } else { 1745 $source->addAttribute($name, $value); 1746 } 1747 } 1748 } 1749 1750 /** 1751 * Merges new elements into a source `<fields>` element. 1752 * 1753 * @param \SimpleXMLElement $source The source element. 1754 * @param \SimpleXMLElement $new The new element to merge. 1755 * 1756 * @return void 1757 * 1758 * @since 1.7.0 1759 */ 1760 protected static function mergeNodes(\SimpleXMLElement $source, \SimpleXMLElement $new) 1761 { 1762 // The assumption is that the inputs are at the same relative level. 1763 // So we just have to scan the children and deal with them. 1764 1765 // Update the attributes of the child node. 1766 foreach ($new->attributes() as $name => $value) { 1767 if (isset($source[$name])) { 1768 $source[$name] = (string) $value; 1769 } else { 1770 $source->addAttribute($name, $value); 1771 } 1772 } 1773 1774 foreach ($new->children() as $child) { 1775 $type = $child->getName(); 1776 $name = $child['name']; 1777 1778 // Does this node exist? 1779 $fields = $source->xpath($type . '[@name="' . $name . '"]'); 1780 1781 if (empty($fields)) { 1782 // This node does not exist, so add it. 1783 self::addNode($source, $child); 1784 } else { 1785 // This node does exist. 1786 switch ($type) { 1787 case 'field': 1788 self::mergeNode($fields[0], $child); 1789 break; 1790 1791 default: 1792 self::mergeNodes($fields[0], $child); 1793 break; 1794 } 1795 } 1796 } 1797 } 1798 1799 /** 1800 * Returns the value of an attribute of the form itself 1801 * 1802 * @param string $name Name of the attribute to get 1803 * @param mixed $default Optional value to return if attribute not found 1804 * 1805 * @return mixed Value of the attribute / default 1806 * 1807 * @since 3.2 1808 */ 1809 public function getAttribute($name, $default = null) 1810 { 1811 if ($this->xml instanceof \SimpleXMLElement) { 1812 $value = $this->xml->attributes()->$name; 1813 1814 if ($value !== null) { 1815 return (string) $value; 1816 } 1817 } 1818 1819 return $default; 1820 } 1821 1822 /** 1823 * Getter for the form data 1824 * 1825 * @return Registry Object with the data 1826 * 1827 * @since 3.2 1828 */ 1829 public function getData() 1830 { 1831 return $this->data; 1832 } 1833 1834 /** 1835 * Method to get the XML form object 1836 * 1837 * @return \SimpleXMLElement The form XML object 1838 * 1839 * @since 3.2 1840 */ 1841 public function getXml() 1842 { 1843 return $this->xml; 1844 } 1845 1846 /** 1847 * Method to get a form field represented as an XML element object. 1848 * 1849 * @param string $name The name of the form field. 1850 * @param string $group The optional dot-separated form group path on which to find the field. 1851 * 1852 * @return \SimpleXMLElement|boolean The XML element object for the field or boolean false on error. 1853 * 1854 * @since 3.7.0 1855 */ 1856 public function getFieldXml($name, $group = null) 1857 { 1858 return $this->findField($name, $group); 1859 } 1860 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Sep 7 05:41:13 2022 | Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer |