[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/libraries/src/Form/Field/ -> SubformField.php (source)

   1  <?php
   2  
   3  /**
   4   * Joomla! Content Management System
   5   *
   6   * @copyright  (C) 2016 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\Field;
  11  
  12  use Joomla\CMS\Filesystem\Path;
  13  use Joomla\CMS\Form\Form;
  14  use Joomla\CMS\Form\FormField;
  15  use Joomla\Registry\Registry;
  16  
  17  // phpcs:disable PSR1.Files.SideEffects
  18  \defined('JPATH_PLATFORM') or die;
  19  // phpcs:enable PSR1.Files.SideEffects
  20  
  21  /**
  22   * The Field to load the form inside current form
  23   *
  24   * @Example with all attributes:
  25   *  <field name="field-name" type="subform"
  26   *      formsource="path/to/form.xml" min="1" max="3" multiple="true" buttons="add,remove,move"
  27   *      layout="joomla.form.field.subform.repeatable-table" groupByFieldset="false" component="com_example" client="site"
  28   *      label="Field Label" description="Field Description" />
  29   *
  30   * @since  3.6
  31   */
  32  class SubformField extends FormField
  33  {
  34      /**
  35       * The form field type.
  36       * @var    string
  37       */
  38      protected $type = 'Subform';
  39  
  40      /**
  41       * Form source
  42       * @var string
  43       */
  44      protected $formsource;
  45  
  46      /**
  47       * Minimum items in repeat mode
  48       * @var integer
  49       */
  50      protected $min = 0;
  51  
  52      /**
  53       * Maximum items in repeat mode
  54       * @var integer
  55       */
  56      protected $max = 1000;
  57  
  58      /**
  59       * Layout to render the form
  60       * @var  string
  61       */
  62      protected $layout = 'joomla.form.field.subform.default';
  63  
  64      /**
  65       * Whether group subform fields by it`s fieldset
  66       * @var boolean
  67       */
  68      protected $groupByFieldset = false;
  69  
  70      /**
  71       * Which buttons to show in multiple mode
  72       * @var array $buttons
  73       */
  74      protected $buttons = array('add' => true, 'remove' => true, 'move' => true);
  75  
  76      /**
  77       * Method to get certain otherwise inaccessible properties from the form field object.
  78       *
  79       * @param   string  $name  The property name for which to get the value.
  80       *
  81       * @return  mixed  The property value or null.
  82       *
  83       * @since   3.6
  84       */
  85      public function __get($name)
  86      {
  87          switch ($name) {
  88              case 'formsource':
  89              case 'min':
  90              case 'max':
  91              case 'layout':
  92              case 'groupByFieldset':
  93              case 'buttons':
  94                  return $this->$name;
  95          }
  96  
  97          return parent::__get($name);
  98      }
  99  
 100      /**
 101       * Method to set certain otherwise inaccessible properties of the form field object.
 102       *
 103       * @param   string  $name   The property name for which to set the value.
 104       * @param   mixed   $value  The value of the property.
 105       *
 106       * @return  void
 107       *
 108       * @since   3.6
 109       */
 110      public function __set($name, $value)
 111      {
 112          switch ($name) {
 113              case 'formsource':
 114                  $this->formsource = (string) $value;
 115  
 116                  // Add root path if we have a path to XML file
 117                  if (strrpos($this->formsource, '.xml') === \strlen($this->formsource) - 4) {
 118                      $this->formsource = Path::clean(JPATH_ROOT . '/' . $this->formsource);
 119                  }
 120  
 121                  break;
 122  
 123              case 'min':
 124                  $this->min = (int) $value;
 125                  break;
 126  
 127              case 'max':
 128                  if ($value) {
 129                      $this->max = max(1, (int) $value);
 130                  }
 131                  break;
 132  
 133              case 'groupByFieldset':
 134                  if ($value !== null) {
 135                      $value = (string) $value;
 136                      $this->groupByFieldset = !($value === 'false' || $value === 'off' || $value === '0');
 137                  }
 138                  break;
 139  
 140              case 'layout':
 141                  $this->layout = (string) $value;
 142  
 143                  // Make sure the layout is not empty.
 144                  if (!$this->layout) {
 145                      // Set default value depend from "multiple" mode
 146                      $this->layout = !$this->multiple ? 'joomla.form.field.subform.default' : 'joomla.form.field.subform.repeatable';
 147                  }
 148  
 149                  break;
 150  
 151              case 'buttons':
 152                  if (!$this->multiple) {
 153                      $this->buttons = array();
 154                      break;
 155                  }
 156  
 157                  if ($value && !\is_array($value)) {
 158                      $value = explode(',', (string) $value);
 159                      $value = array_fill_keys(array_filter($value), true);
 160                  }
 161  
 162                  if ($value) {
 163                      $value = array_merge(array('add' => false, 'remove' => false, 'move' => false), $value);
 164                      $this->buttons = $value;
 165                  }
 166  
 167                  break;
 168  
 169              case 'value':
 170                  // We allow a json encoded string or an array
 171                  if (is_string($value)) {
 172                      $value = json_decode($value, true);
 173                  }
 174  
 175                  $this->value = $value !== null ? (array) $value : null;
 176  
 177                  break;
 178  
 179              default:
 180                  parent::__set($name, $value);
 181          }
 182      }
 183  
 184      /**
 185       * Method to attach a Form object to the field.
 186       *
 187       * @param   \SimpleXMLElement  $element  The SimpleXMLElement object representing the <field /> tag for the form field object.
 188       * @param   mixed              $value    The form field value to validate.
 189       * @param   string             $group    The field name group control value.
 190       *
 191       * @return  boolean  True on success.
 192       *
 193       * @since   3.6
 194       */
 195      public function setup(\SimpleXMLElement $element, $value, $group = null)
 196      {
 197          if (!parent::setup($element, $value, $group)) {
 198              return false;
 199          }
 200  
 201          foreach (array('formsource', 'min', 'max', 'layout', 'groupByFieldset', 'buttons') as $attributeName) {
 202              $this->__set($attributeName, $element[$attributeName]);
 203          }
 204  
 205          if ((string) $element['fieldname']) {
 206              $this->__set('fieldname', $element['fieldname']);
 207          }
 208  
 209          if ($this->value && \is_string($this->value)) {
 210              // Guess here is the JSON string from 'default' attribute
 211              $this->value = json_decode($this->value, true);
 212          }
 213  
 214          if (!$this->formsource && $element->form) {
 215              // Set the formsource parameter from the content of the node
 216              $this->formsource = $element->form->saveXML();
 217          }
 218  
 219          return true;
 220      }
 221  
 222      /**
 223       * Method to get the field input markup.
 224       *
 225       * @return  string  The field input markup.
 226       *
 227       * @since   3.6
 228       */
 229      protected function getInput()
 230      {
 231          // Prepare data for renderer
 232          $data    = $this->getLayoutData();
 233          $tmpl    = null;
 234          $control = $this->name;
 235  
 236          try {
 237              $tmpl  = $this->loadSubForm();
 238              $forms = $this->loadSubFormData($tmpl);
 239          } catch (\Exception $e) {
 240              return $e->getMessage();
 241          }
 242  
 243          $data['tmpl']      = $tmpl;
 244          $data['forms']     = $forms;
 245          $data['min']       = $this->min;
 246          $data['max']       = $this->max;
 247          $data['control']   = $control;
 248          $data['buttons']   = $this->buttons;
 249          $data['fieldname'] = $this->fieldname;
 250          $data['fieldId']   = $this->id;
 251          $data['groupByFieldset'] = $this->groupByFieldset;
 252  
 253          /**
 254           * For each rendering process of a subform element, we want to have a
 255           * separate unique subform id present to could distinguish the eventhandlers
 256           * regarding adding/moving/removing rows from nested subforms from their parents.
 257           */
 258          static $unique_subform_id = 0;
 259          $data['unique_subform_id'] = ('sr-' . ($unique_subform_id++));
 260  
 261          // Prepare renderer
 262          $renderer = $this->getRenderer($this->layout);
 263  
 264          // Allow to define some Layout options as attribute of the element
 265          if ($this->element['component']) {
 266              $renderer->setComponent((string) $this->element['component']);
 267          }
 268  
 269          if ($this->element['client']) {
 270              $renderer->setClient((string) $this->element['client']);
 271          }
 272  
 273          // Render
 274          $html = $renderer->render($data);
 275  
 276          // Add hidden input on front of the subform inputs, in multiple mode
 277          // for allow to submit an empty value
 278          if ($this->multiple) {
 279              $html = '<input name="' . $this->name . '" type="hidden" value="">' . $html;
 280          }
 281  
 282          return $html;
 283      }
 284  
 285      /**
 286       * Method to get the name used for the field input tag.
 287       *
 288       * @param   string  $fieldName  The field element name.
 289       *
 290       * @return  string  The name to be used for the field input tag.
 291       *
 292       * @since   3.6
 293       */
 294      protected function getName($fieldName)
 295      {
 296          $name = '';
 297  
 298          // If there is a form control set for the attached form add it first.
 299          if ($this->formControl) {
 300              $name .= $this->formControl;
 301          }
 302  
 303          // If the field is in a group add the group control to the field name.
 304          if ($this->group) {
 305              // If we already have a name segment add the group control as another level.
 306              $groups = explode('.', $this->group);
 307  
 308              if ($name) {
 309                  foreach ($groups as $group) {
 310                      $name .= '[' . $group . ']';
 311                  }
 312              } else {
 313                  $name .= array_shift($groups);
 314  
 315                  foreach ($groups as $group) {
 316                      $name .= '[' . $group . ']';
 317                  }
 318              }
 319          }
 320  
 321          // If we already have a name segment add the field name as another level.
 322          if ($name) {
 323              $name .= '[' . $fieldName . ']';
 324          } else {
 325              $name .= $fieldName;
 326          }
 327  
 328          return $name;
 329      }
 330  
 331      /**
 332       * Loads the form instance for the subform.
 333       *
 334       * @return  Form  The form instance.
 335       *
 336       * @throws  \InvalidArgumentException if no form provided.
 337       * @throws  \RuntimeException if the form could not be loaded.
 338       *
 339       * @since   3.9.7
 340       */
 341      public function loadSubForm()
 342      {
 343          $control = $this->name;
 344  
 345          if ($this->multiple) {
 346              $control .= '[' . $this->fieldname . 'X]';
 347          }
 348  
 349          // Prepare the form template
 350          $formname = 'subform.' . str_replace(array('jform[', '[', ']'), array('', '.', ''), $this->name);
 351          $tmpl     = Form::getInstance($formname, $this->formsource, array('control' => $control));
 352  
 353          return $tmpl;
 354      }
 355  
 356      /**
 357       * Binds given data to the subform and its elements.
 358       *
 359       * @param   Form  $subForm  Form instance of the subform.
 360       *
 361       * @return  Form[]  Array of Form instances for the rows.
 362       *
 363       * @since   3.9.7
 364       */
 365      protected function loadSubFormData(Form $subForm)
 366      {
 367          $value = $this->value ? (array) $this->value : array();
 368  
 369          // Simple form, just bind the data and return one row.
 370          if (!$this->multiple) {
 371              $subForm->bind($value);
 372  
 373              return array($subForm);
 374          }
 375  
 376          // Multiple rows possible: Construct array and bind values to their respective forms.
 377          $forms = array();
 378          $value = array_values($value);
 379  
 380          // Show as many rows as we have values, but at least min and at most max.
 381          $c = max($this->min, min(\count($value), $this->max));
 382  
 383          for ($i = 0; $i < $c; $i++) {
 384              $control  = $this->name . '[' . $this->fieldname . $i . ']';
 385              $itemForm = Form::getInstance($subForm->getName() . $i, $this->formsource, array('control' => $control));
 386  
 387              if (!empty($value[$i])) {
 388                  $itemForm->bind($value[$i]);
 389              }
 390  
 391              $forms[] = $itemForm;
 392          }
 393  
 394          return $forms;
 395      }
 396  
 397      /**
 398       * Method to filter a field value.
 399       *
 400       * @param   mixed     $value  The optional value to use as the default for the field.
 401       * @param   string    $group  The optional dot-separated form group path on which to find the field.
 402       * @param   Registry  $input  An optional Registry object with the entire data set to filter
 403       *                            against the entire form.
 404       *
 405       * @return  mixed   The filtered value.
 406       *
 407       * @since   4.0.0
 408       * @throws  \UnexpectedValueException
 409       */
 410      public function filter($value, $group = null, Registry $input = null)
 411      {
 412          // Make sure there is a valid SimpleXMLElement.
 413          if (!($this->element instanceof \SimpleXMLElement)) {
 414              throw new \UnexpectedValueException(sprintf('%s::filter `element` is not an instance of SimpleXMLElement', \get_class($this)));
 415          }
 416  
 417          // Get the field filter type.
 418          $filter = (string) $this->element['filter'];
 419  
 420          if ($filter !== '') {
 421              return parent::filter($value, $group, $input);
 422          }
 423  
 424          // Dirty way of ensuring required fields in subforms are submitted and filtered the way other fields are
 425          $subForm = $this->loadSubForm();
 426  
 427          // Subform field may have a default value, that is a JSON string
 428          if ($value && is_string($value)) {
 429              $value = json_decode($value, true);
 430  
 431              // The string is invalid json
 432              if (!$value) {
 433                  return null;
 434              }
 435          }
 436  
 437          if ($this->multiple) {
 438              $return = [];
 439  
 440              if ($value) {
 441                  foreach ($value as $key => $val) {
 442                      $return[$key] = $subForm->filter($val);
 443                  }
 444              }
 445          } else {
 446              $return = $subForm->filter($value);
 447          }
 448  
 449          return $return;
 450      }
 451  }


Generated: Wed Sep 7 05:41:13 2022 Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer