[ 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) 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 }
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 |