<?php /** * @package VikWP - Libraries * @subpackage adapter.form * @author E4J s.r.l. * @copyright Copyright (C) 2023 E4J s.r.l. All Rights Reserved. * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL * @link https://vikwp.com */ // No direct access defined('ABSPATH') or die('No script kiddies please!'); JLoader::import('adapter.form.field'); /** * Form class to handle XML forms. * * This class implements a robust API for constructing, populating, filtering, and validating forms. * It uses XML definitions to construct form fields and a variety of field and rule classes to * render and validate the form. * * @since 10.0 */ class JForm { /** * A list of JForm instances * * @var array */ protected static $forms = array(); /** * The form XML definition. * * @var SimpleXMLElement */ protected $xml; /** * The form name. * * @var string * @since 10.1.20 */ protected $name; /** * The form options. * * @var array * @since 10.1.20 */ protected $options; /** * Method to get an instance of a form. * * @param string $name The name of the form. * @param string $data The name of an XML file or string to load as the form definition. * @param array $options An array of form options. * * @return JForm A new JForm instance. * * @throws InvalidArgumentException if no data provided. * @throws RuntimeException if the form could not be loaded. */ public static function getInstance($name, $data = null, $options = array()) { // only instantiate the form if it does not already exist if (!isset(static::$forms[$name])) { if (is_string($data)) { $data = trim($data); } if (empty($data)) { // no provided data, throw an exception throw new InvalidArgumentException( sprintf('JForm::getInstance(%s, *%s*)', $name, gettype($data) ), 400 ); } // instantiate the form. static::$forms[$name] = new static($name, $options); // if the string starts with '<' load the XML as string if ($data instanceof SimpleXMLElement || substr($data, 0, 1) == '<') { if (static::$forms[$name]->load($data) == false) { throw new RuntimeException('JForm::getInstance() could not load form.', 500); } } else { if (static::$forms[$name]->loadFile($data) == false) { throw new RuntimeException(sprintf('JForm::getInstance() could not load file [%s].', $data), 500); } } } return static::$forms[$name]; } /** * Class constructor. * * @param string $name The name of the form. * @param array $options An array of form options. * * @since 10.1.20 */ public function __construct($name, $options = array()) { $this->name = $name; $this->options = (array) $options; } /** * Returns a list of fieldsets. * If the name is provided, returns only the match. * * @param string $set The fieldset name. * * @return array A list of fieldsets. */ public function getFieldset($set = null) { if (is_null($set)) { // return all fieldsets return $this->xml->xpath('//fieldset'); } return $this->xml->xpath('//fieldset[@name="' . $set . '"]'); } /** * Returns a list of fields that match the query. * * @param string $val The field key value. * @param string $key The field key in which to search (name by default). * * @return array The matching XML elements. */ public function getFields($val = null, $key = 'name') { if (is_null($val)) { // do not filter fields return $this->xml->xpath('//field'); } return $this->xml->xpath('//field[@' . $key . '="' . $val . '"]'); } /** * Returns the specified field. * * @param string $val The field key value. * @param string $key The field key in which to search (name by default). * * @return mixed The field XML element on success, otherwise null. * * @uses getFields() */ public function getField($val, $key = 'name') { $fields = $this->getFields($val, $key); // return first element if any, otherwise null return array_shift($fields); } /** * Returns the loaded XML object. * * @return SimpleXMLElement */ public function getXml() { return $this->xml; } /** * Method to load the form description from an XML string or object. * * @param string $data The name of an XML string or object. * * @return boolean True on success, otherwise false. */ public function load($data) { // if the data to load isn't already an XML element or string return false if (!($data instanceof SimpleXMLElement) && !is_string($data)) { return false; } // attempt to load the XML if a string if (is_string($data)) { $data = new SimpleXMLElement($data); // make sure the XML loaded correctly if (!$data) { return false; } } // if we have no XML definition at this point let's make sure we get one if (empty($this->xml)) { $this->xml = $data; } // search for any fieldset that mentions "addfieldpath" $nodes = $this->xml->xpath('//fieldset[@addfieldpath!=""]'); // iterate the nodes found foreach ($nodes as $node) { $path = (string) $node->attributes()->addfieldpath; /** * Check if the [addfieldpath] attribute is defined using a Joomla path. * For example: * - /administrator/components/com_[option]/[path] * - /components/com_[option]/[path] * * @since 10.1.16 */ if (preg_match("/^\/(administrator)?\/components\/com_([a-z0-9_]+)\/?(.*)/i", $path, $parts)) { // starts with option name $path = $parts[2] . '/'; if ($parts[1] === 'administrator') { // use admin folder $path .= 'admin'; } else { // use site folder $path .= 'site'; } // concat remaining path $path .= '/' . $parts[3]; } else if (!empty($this->options['client'])) { /** * Try to check if we have a caller within the options array. * * @since 10.1.20 */ $path = $this->options['client'] . '/' . ltrim($path, '/'); } $path = WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, ltrim($path, '/')); // update form fields paths with a new path in which to search for custom handlers JFormField::addIncludePath($path); } return true; } /** * Method to load the form description from an XML file. * * @param string $file The filesystem path of an XML file. * * @return boolean True on success, otherwise false. * * @uses load() */ public function loadFile($file) { // make sure the file exists if (!is_file($file)) { return false; } // attempt to load the XML file $xml = simplexml_load_file($file); return $this->load($xml); } /** * Renders the form layout. * * @param object $data The object data to bind: * Property name = Field name; * Property value = Field value. * * @return string The HTML form layout. * * @uses renderFieldset() */ public function renderForm($data = null) { return $this->renderFieldset(null, $data); } /** * Renders the layout of the given form fieldset. * If fieldset is not given, renders all the fieldsets. * * @param string $set The fielset name. * @param object $data The object data to bind: * Property name = Field name; * Property value = Field value. * * @return string The HTML fieldset(s) layout. * * @uses getFieldset() * @uses renderField() */ public function renderFieldset($set = null, $data = null) { // get the fieldsets $fieldsets = $this->getFieldset($set); $html = ''; // iterate the fieldsets foreach ($fieldsets as $fieldset) { $setname = (string) $fieldset->attributes()->name; $setname = 'COM_MENUS_' . strtoupper($setname) . '_FIELDSET_LABEL'; /** * Do not use a fieldset name in case the title should not be * displayed or in case the translation is missing. * * @since 10.1.29 */ if ($fieldset->attributes()->hidden || JText::translate($setname) == $setname) { $setname = ''; } // render fieldset opening $html .= JHtml::fetch('layoutfile', 'html.form.fieldset.open')->render(array('name' => $setname)); // iterate the fieldset children foreach ($fieldset->field as $field) { $attrs = $field->attributes(); $name = (string) $attrs->name; $args = array(); $args['id'] = (string) $attrs->id; $args['label'] = (string) $attrs->label; $args['description'] = (string) $attrs->description; $args['required'] = ((string) $attrs->required) === 'true'; /** * Open control only in case the input shouldn't be hidden. * * @since 10.1.21 */ if ($attrs->type != 'hidden' && $attrs->type != 'spacer' && empty($attrs->hidden)) { // open control $html .= JHtml::fetch('layoutfile', 'html.form.control.open')->render($args); } // try to check if the value should be bound $val = isset($data->{$name}) ? $data->{$name} : null; // render field $html .= $this->renderField($field, array('value' => $val)); /** * Close control only in case the input shouldn't be hidden. * * @since 10.1.21 */ if ($attrs->type != 'hidden' && $attrs->type != 'spacer' && empty($attrs->hidden)) { // close control $html .= JHtml::fetch('layoutfile', 'html.form.control.close')->render($args); } } // render fieldset closing $html .= JHtml::fetch('layoutfile', 'html.form.fieldset.close')->render(); } return $html; } /** * Renders the specified field. * * @param mixed $field The field (or its name) to render. * @param mixed $data The value (or a list of data) to bind. * * @return string The rendered field. * * @uses getField() */ public function renderField($field, $data = null) { // get field XML element if the name was provided if (is_string($field)) { $field = $this->getField($field); } // get form field $field = JFormField::getInstance($field); /** * Assign field to this form. * * @since 10.1.31 */ $field->setForm($this); // bind data if set if ($data) { // if scalar value, setup value array if (is_scalar($data)) { $data = array('value' => $data); } // iterate the data to bind foreach ($data as $k => $v) { // bind attribute only if NOT NULL if (!is_null($v)) { $field->bind($v, $k); } } } /** * When specified, instruct the field that the layout * should be drawn from the given client (plugin name). * * @since 10.1.31 */ if (isset($this->options['client'])) { $field->modowner = $this->options['client']; } // get the field class and do the rendering return $field->render(); } /** * Method used to bind data to the form. * * @param mixed $data An array or object of data to bind to the form. * * @return boolean True on success. * * @since 10.1.27 */ public function bind($data) { // Make sure there is a valid JForm XML document. if (!($this->xml instanceof \SimpleXMLElement)) { return false; } // The data must be an object or array. if (!is_object($data) && !is_array($data)) { return false; } // iterate field by field foreach ($data as $name => $value) { /** * Update only in case the value is NOT NULL. * * @since 10.1.29 */ if (!is_null($value)) { // find field by name $field = $this->getField($name); if ($field) { // field found, update XML element by injecting // the specified value $field['value'] = $value; } } } return true; } /** * Method to get the form control. This string serves as a container for all form fields. For * example, if there is a field named 'foo' and a field named 'bar' and the form control is * empty the fields will be rendered like: `<input name="foo" />` and `<input name="bar" />`. If * the form control is set to 'jform' however, the fields would be rendered like: * `<input name="jform[foo]" />` and `<input name="jform[bar]" />`. * * @return string The form control string. * * @since 10.1.31 */ public function getFormControl() { return (string) isset($this->options['control']) ? $this->options['control'] : ''; } /** * Method to set the form control. * * @param string $control The form control. * * @return void * * @since 10.1.31 */ public function setFormControl($control) { $this->options['control'] = $control; } }