Rev 1 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | Download
<?php namespace ProcessWire;/*** ProcessWire InputfieldWrapper** ProcessWire 3.x, Copyright 2022 by Ryan Cramer* https://processwire.com** About InputfieldWrapper* =======================* A type of Inputfield that is designed specifically to wrap other Inputfields.* The most common example of an InputfieldWrapper is a <form>.** #pw-summary A type of Inputfield that contains other Inputfield objects as children. Commonly a form or a fieldset.** InputfieldWrapper is not designed to render an Inputfield specifically, but you can set a value attribute* containing content that will be rendered before the wrapper.** @property bool $renderValueMode True when only rendering values, i.e. no inputs (default=false). #pw-internal* @property bool $quietMode True to suppress label, description and notes, often combined with renderValueMode (default=false). #pw-internal* @property int $columnWidthSpacing Percentage spacing between columns or 0 for none. Default pulled from `$config->inputfieldColumnWidthSpacing`. #pw-internal* @property bool $useDependencies Whether or not to consider `showIf` and `requiredIf` dependencies during processing (default=true). #pw-internal* @property bool|null $InputfieldWrapper_isPreRendered Whether or not children have been pre-rendered (internal use only) #pw-internal* @property InputfieldsArray|null $children Inputfield instances that are direct children of this InputfieldWrapper. #pw-group-properties** @method string renderInputfield(Inputfield $inputfield, $renderValueMode = false) #pw-group-output* @method Inputfield new($typeName, $name = '', $label = '', array $settings = []) #pw-group-manipulation** @property InputfieldAsmSelect $InputfieldAsmSelect* @property InputfieldButton $InputfieldButton* @property InputfieldCheckbox $InputfieldCheckbox* @property InputfieldCheckboxes $InputfieldCheckboxes* @property InputfieldCKEditor $InputfieldCkeditor* @property InputfieldCommentsAdmin $InputfieldCommentsAdmin* @property InputfieldDatetime $InputfieldDatetime* @property InputfieldEmail $InputfieldEmail* @property InputfieldFieldset $InputfieldFieldset* @property InputfieldFieldsetClose $InputfieldlFieldsetClose* @property InputfieldFieldsetOpen $InputfieldFieldsetOpen* @property InputfieldFieldsetTabOpen $InputfieldFieldsetTabOpen* @property InputfieldFile $InputfieldFile* @property InputfieldFloat $InputfieldFloat* @property InputfieldForm $InputfieldForm* @property InputfieldHidden $InputfieldHidden* @property InputfieldIcon $InputfieldIcon* @property InputfieldImage $InputfieldImage* @property InputfieldInteger $InputfieldInteger* @property InputfieldMarkup $InputfieldMarkup* @property InputfieldName $InputfieldName* @property InputfieldPage $InputfieldPage* @property InputfieldPageAutocomplete $InputfieldPageAutocomplete* @property InputfieldPageListSelect $InputfieldPageListSelect* @property InputfieldPageListSelectMultiple $InputfieldPageListSelectMultiple* @property InputfieldPageName $InputfieldPageName* @property InputfieldPageTable $InputfieldPageTable* @property InputfieldPageTitle $InputfieldPageTitle* @property InputfieldPassword $InputfieldPassword* @property InputfieldRadios $InputfieldRadios* @property InputfieldRepeater $InputfieldRepeater* @property InputfieldSelect $InputfieldSelect* @property InputfieldSelectMultiple $InputfieldSelectMultiple* @property InputfieldSelector $InputfieldSelector* @property InputfieldSubmit $InputfieldSubmit* @property InputfieldText $InputfieldText* @property InputfieldTextarea $InputfieldTextarea* @property InputfieldTextTags $InputfieldTextTags* @property InputfieldToggle $InputfieldToggle* @property InputfieldURL $InputfieldURL* @property InputfieldWrapper $InputfieldWrapper**/class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggregate {/*** Set to true for debugging optimization of property accesses** #pw-internal**/const debugPropertyAccess = false;/*** Markup used during the render() method - customize with InputfieldWrapper::setMarkup($array)**/static protected $defaultMarkup = array('list' => "<ul {attrs}>{out}</ul>",'item' => "<li {attrs}>{out}</li>",'item_label' => "<label class='InputfieldHeader ui-widget-header{class}' for='{for}'>{out}</label>",'item_label_hidden' => "<label class='InputfieldHeader InputfieldHeaderHidden ui-widget-header{class}'><span>{out}</span></label>",'item_content' => "<div class='InputfieldContent ui-widget-content{class}'>{out}</div>",'item_error' => "<p class='InputfieldError ui-state-error'><i class='fa fa-fw fa-flash'></i><span>{out}</span></p>",'item_description' => "<p class='description'>{out}</p>",'item_head' => "<h2>{out}</h2>",'item_notes' => "<p class='notes'>{out}</p>",'item_detail' => "<p class='detail'>{out}</p>",'item_icon' => "<i class='fa fa-fw fa-{name}'></i> ",'item_toggle' => "<i class='toggle-icon fa fa-fw fa-angle-down' data-to='fa-angle-down fa-angle-right'></i>",// ALSO:// InputfieldAnything => array( any of the properties above to override on a per-Inputifeld basis));static protected $markup = array();/*** Classes used during the render() method - customize with InputfieldWrapper::setClasses($array)**/static protected $defaultClasses = array('form' => '', // additional clases for InputfieldForm (optional)'list' => 'Inputfields','list_clearfix' => 'ui-helper-clearfix','item' => 'Inputfield {class} Inputfield_{name} ui-widget','item_label' => '', // additional classes for InputfieldHeader (optional)'item_content' => '', // additional classes for InputfieldContent (optional)'item_required' => 'InputfieldStateRequired', // class is for Inputfield'item_error' => 'ui-state-error InputfieldStateError', // note: not the same as markup[item_error], class is for Inputfield'item_collapsed' => 'InputfieldStateCollapsed','item_column_width' => 'InputfieldColumnWidth','item_column_width_first' => 'InputfieldColumnWidthFirst','item_show_if' => 'InputfieldStateShowIf','item_required_if' => 'InputfieldStateRequiredIf'// ALSO:// InputfieldAnything => array( any of the properties above to override on a per-Inputifeld basis));static protected $classes = array();/*** Instance of InputfieldsArray, if this Inputfield contains child Inputfields** @var InputfieldsArray**/protected $children;/*** Array of Inputfields that had their processing delayed by dependencies.**/protected $delayedChildren = array();/*** Label displayed when a value is required but missing**/protected $requiredLabel = 'Missing required value';/*** Whether or not column width is handled internally** @var bool**/protected $useColumnWidth = true;/*** Construct the Inputfield, setting defaults for all properties**/public function __construct() {parent::__construct();$this->children = new InputfieldsArray();$this->set('skipLabel', Inputfield::skipLabelFor);$this->set('useDependencies', true); // whether or not to use consider field dependencies during processing$this->set('renderValueMode', false);$this->set('quietMode', false); // suppress label, description and notes$this->set('columnWidthSpacing', 0);}/*** Wired to API**/public function wired() {$config = $this->wire()->config;$this->wire($this->children);$this->requiredLabel = $this->_('Missing required value');$columnWidthSpacing = $config->inputfieldColumnWidthSpacing;$columnWidthSpacing = is_null($columnWidthSpacing) ? 1 : (int) $columnWidthSpacing;if($columnWidthSpacing > 0) $this->set('columnWidthSpacing', $columnWidthSpacing);$columnWidthSpacing = null;$settings = $config->InputfieldWrapper;if(is_array($settings)) {foreach($settings as $key => $value) {if($key == 'requiredLabel') {$this->requiredLabel = $value;} else if($key == 'useColumnWidth') {$this->useColumnWidth = $value;} else {$this->set($key, $value);}}}parent::wired();}/*** Get a child Inputfield having a name attribute matching the given $key.** This method can also get settings, attributes or API variables, so long as they don't* collide with an Inputfield name. For that reason, you may prefer to use the `Inputfield::getSetting()`,* `Inputfield::attr()` or `Wire::wire()` methods for those other purposes.** If you want a method that can only return a matching Inputfield object, use the* `InputfieldWrapper::getChildByName()` method .** #pw-group-retrieval-and-traversal** @param string $key Name of Inputfield or setting/property to retrieve.* @return Inputfield|mixed* @see InputfieldWrapper::getChildByName()* @throws WireException Only in core development/debugging, otherwise does not throw exceptions.**/public function get($key) {$inputfield = $this->getChildByName($key);if($inputfield) return $inputfield;if(self::debugPropertyAccess) throw new WireException("Access of attribute or setting: $key");$value = $this->wire($key);if($value) return $value;if($key === 'children') return $this->children();if(($value = parent::get($key)) !== null) return $value;return null;}/*** Provides direct reference to attributes and settings, and falls back to Inputfield children** This is different behavior from the get() method.** @param string $key* @return mixed|null**/public function __get($key) {if($key === 'children') return $this->children();if(strpos($key, 'Inputfield') === 0 && strlen($key) > 10) {if($key === 'InputfieldWrapper') return $this->wire(new InputfieldWrapper());$value = $this->wire()->modules->get($key);if($value && $value instanceof Inputfield) return $value;if(wireClassExists($key)) return $this->wire(new $key());$value = null;}$value = parent::get($key);if(is_null($value)) $value = $this->getChildByName($key);return $value;}/*** Add an Inputfield item as a child (also accepts array definition)** Since 3.0.110: If given a string value, it is assumed to be an Inputfield type that you* want to add. In that case, it will create the Inputfield and return it instead of $this.** #pw-group-manipulation** @param Inputfield|array|string $item* @return Inputfield|InputfieldWrapper|$this* @see InputfieldWrapper::import()**/public function add($item) {if(is_string($item)) {return $this->___new($item);} else if(is_array($item)) {$this->importArray($item);} else {$this->children()->add($item);$item->setParent($this);}return $this;}/*** Create a new Inputfield, add it to this InputfieldWrapper, and return the new Inputfield** - Only the $typeName argument is required.* - You may optionally substitute the $settings argument for the $name or $label arguments.* - You may optionally substitute Inputfield “description” property for $settings argument.** #pw-group-manipulation** @param string $typeName Inputfield type, i.e. “InputfieldCheckbox” or just “checkbox” for short.* @param string|array $name Name of input (or substitute $settings here).* @param string|array $label Label for input (or substitute $settings here).* @param array|string $settings Settings to add to Inputfield (optional). Or if string, assumed to be “description”.* @return Inputfield|InputfieldSelect|InputfieldWrapper An Inputfield instance ready to populate with additional properties/attributes.* @throws WireException If you request an unknown Inputfield type* @since 3.0.110**/public function ___new($typeName, $name = '', $label = '', $settings = array()) {if(is_array($name)) {$settings = $name;$name = '';} else if(is_array($label)) {$settings = $label;$label = '';}if(strpos($typeName, 'Inputfield') !== 0) {$typeName = "Inputfield" . ucfirst($typeName);}/** @var Inputfield|InputfieldSelect|InputfieldWrapper $inputfield */$inputfield = $this->wire('modules')->getModule($typeName);if(!$inputfield && wireClassExists($typeName)) {$inputfield = $this->wire(new $typeName());}if(!$inputfield || !$inputfield instanceof Inputfield) {throw new WireException("Unknown Inputfield type: $typeName");}if(strlen($name)) $inputfield->attr('name', $name);if(strlen($label)) $inputfield->label = $label;if(is_array($settings)) {foreach($settings as $key => $value) {$inputfield->set($key, $value);}} else if(is_string($settings)) {$inputfield->description = $settings;}$this->add($inputfield);return $inputfield;}/*** Import the given Inputfield items as children** If given an `InputfieldWrapper`, it will import the children of it and* exclude the wrapper itself. This is different from `InputfieldWrapper::add()`* in that add() would add the wrapper, not just the children. See also* the `InputfieldWrapper::importArray()` method.** #pw-group-manipulation** @param InputfieldWrapper|array|InputfieldsArray $items Wrapper containing items to add* @return $this* @throws WireException* @see InputfieldWrapper::add(), InputfieldWrapper::importArray()**/public function import($items) {if($items instanceof InputfieldWrapper || $items instanceof InputfieldsArray) {foreach($items as $item) {$this->add($item);}} else if(is_array($items)) {$this->importArray($items);} else if($items instanceof Inputfield) {$this->add($items);} else {throw new WireException("InputfieldWrapper::import() requires InputfieldWrapper, InputfieldsArray, array, or Inputfield");}return $this;}/*** Prepend an Inputfield to this instance’s children.** #pw-group-manipulation** @param Inputfield $item Item to prepend* @return $this**/public function prepend(Inputfield $item) {$item->setParent($this);$this->children()->prepend($item);return $this;}/*** Append an Inputfield to this instance’s children.** #pw-group-manipulation** @param Inputfield $item Item to append* @return $this**/public function append(Inputfield $item) {$item->setParent($this);$this->children()->append($item);return $this;}/*** Insert new or existing Inputfield before or after another** @param Inputfield|array|string $item New or existing item Inputfield, name, or new item array to insert.* @param Inputfield|string $existingItem Existing item or item name you want to insert before.* @param bool $before True to insert before, false to insert after (default=false).* @return $this* @throws WireException* @since 3.0.196**/public function insert($item, $existingItem, $before = false) {$children = $this->children();if($existingItem instanceof Inputfield) {// ok} else if(is_string($existingItem)) {$name = $existingItem;$existingItem = $this->getByName($name);if(!$existingItem) throw new WireException("Cannot find Inputfield[name=$name] to insert");} else {throw new WireException('Invalid value for $existingItem argument');}if(is_array($item)) {// new item definitionif(isset($item['name'])) {// first check if there's an existing item with this name$f = $this->getByName($item['name']);if($f) return $this->insert($f, $existingItem, $before);}$n = $children->count();$this->add($item);if($children->count() > $n) {// new item was added$item = $children->last();$children->remove($item);} else {throw new WireException('Unable to insert new item: ' . print_r($item, true));}} else if(!$item instanceof Inputfield) {// get item to insert by name$name = (string) $item;$item = $this->getByName($name);if(!$item) {// if named item isn't found, create one$item = $this->___new('text', $name, $name);}}if($children->has($existingItem)) {// existing item is a direct child of this InputfieldWrapper$item->setParent($this);$method = $before ? 'insertBefore' : 'insertAfter';$children->$method($item, $existingItem);} else {// find existing item recursively$f = $this->getByName($existingItem->attr('name'));if($f && $f->parent) {// existing item was found$existingItem = $f;$existingItem->parent->insert($item, $existingItem, $before);} else {// existing item not found, add it as direct child$this->add($existingItem);$existingItem->parent->insert($item, $existingItem, $before);}}return $this;}/*** Insert one Inputfield before one that’s already there.** Note: string or array values for either argument require 3.0.196+.** ~~~~~* // example 1: Get existing Inputfields and insert first_name before last_name* $firstName = $form->getByName('first_name');* $lastName = $form->getByName('last_name');* $form->insertBefore($firstName, $lastName);** // example 2: Same as above but use Inputfield names (3.0.196+)* $form->insertBefore('first_name', 'last_name');** // example 3: Create new Inputfield and insert before last_name (3.0.196+)* $form->insertBefore([ 'type' => 'text', 'name' => 'first_name' ], 'last_name');* ~~~~~** #pw-group-manipulation** @param Inputfield|array|string $item Item to insert* @param Inputfield|string $existingItem Existing item you want to insert before.* @return $this**/public function insertBefore($item, $existingItem) {return $this->insert($item, $existingItem, true);}/*** Insert one Inputfield after one that’s already there.** Note: string or array values for either argument require 3.0.196+.** ~~~~~* // example 1: Get existing Inputfields, insert last_name after first_name* $lastName = $form->getByName('last_name');* $firstName = $form->getByName('first_name');* $form->insertAfter($lastName, $firstName);** // example 2: Same as above but use Inputfield names (3.0.196+)* $form->insertBefore('last_name', 'first_name');** // example 3: Create new Inputfield and insert after first_name (3.0.196+)* $form->insertAfter([ 'type' => 'text', 'name' => 'last_name' ], 'first_name');* ~~~~~** #pw-group-manipulation** @param Inputfield|array|string $item Item to insert* @param Inputfield|string $existingItem Existing item you want to insert after.* @return $this**/public function insertAfter($item, $existingItem) {return $this->insert($item, $existingItem, false);}/*** Remove an Inputfield from this instance’s children.** #pw-group-manipulation** @param Inputfield|string $item Inputfield object or name* @return $this**/public function remove($item) {if(!$item) return $this;if(!$item instanceof Inputfield) {if(!is_string($item)) return $this;$item = $this->getChildByName($item);if(!$item) return $this;}if($this->children()->has($item)) {$this->children()->remove($item);} if($this->getChildByName($item->attr('name')) && $item->parent) {$item->parent->remove($item);}return $this;}/*** Prepare children for rendering by creating any fieldset groups**/protected function preRenderChildren() {if($this->getSetting('InputfieldWrapper_isPreRendered')) return $this->children();$children = $this->wire(new InputfieldWrapper());$wrappers = array($children);foreach($this->children() as $inputfield) {$wrapper = end($wrappers);if($inputfield instanceof InputfieldFieldsetClose) {if(count($wrappers) > 1) array_pop($wrappers);continue;} else if($inputfield instanceof InputfieldFieldsetOpen) {$inputfield->set('InputfieldWrapper_isPreRendered', true);array_push($wrappers, $inputfield);}$inputfield->unsetParent();$wrapper->add($inputfield);}return $children;}/*** Cached class parents indexed by Inputfield class name** @var array**/static protected $classParents = array();/*** Get array of parent Inputfield classes for given Inputfield (excluding the base Inputfield class)** @param Inputfield|string $inputfield* @return array**/protected function classParents($inputfield) {$p = &self::$classParents;$c = is_object($inputfield) ? $inputfield->className() : $inputfield;if(!isset($p[$c])) {$p[$c] = array();foreach(wireClassParents($inputfield) as $parentClass) {if(strpos($parentClass, 'Inputfield') !== 0 || $parentClass === 'Inputfield') break;$p[$c][] = $parentClass;}}return $p[$c];}/*** Prepare Inputfield for attributes used during rendering** #pw-internal** @param Inputfield $inputfield* @param array $markup* @param array $classes* @since 3.0.144**/private function attributeInputfield(Inputfield $inputfield, &$markup, &$classes) {$inputfieldClass = $inputfield->className();$markupTemplate = array('attr' => array(), 'wrapAttr' => array(), 'set' => array());$markupKeys = array($inputfieldClass, "name=$inputfield->name", "id=$inputfield->id");$classKeys = array('class', 'wrapClass', 'headerClass', 'contentClass');$addClasses = array();$attr = array();$wrapAttr = array();$sets = array();foreach($markupKeys as $key) {if(isset($markup[$key])) $markup = array_merge($markup, $markup[$key]);if(isset($classes[$key])) $classes = array_merge($classes, $classes[$key]);}foreach(array_merge($this->classParents($inputfield), $markupKeys) as $key) {if(!isset($markup[$key])) continue;$markupParent = array_merge($markupTemplate, $markup[$key]);foreach($classKeys as $classKey) {if(!empty($markupParent[$classKey])) {$addClasses[$classKey] = $markupParent[$classKey];}}foreach($markupParent['attr'] as $k => $v) {$attr[$k] = $v;}foreach($markupParent['wrapAttr'] as $k => $v) {$wrapAttr[$k] = $v;}foreach($markupParent['set'] as $k => $v) {$sets[$k] = $v;}}foreach($attr as $attrName => $attrVal) {$inputfield->attr($attrName, $attrVal);}foreach($wrapAttr as $attrName => $attrVal) {$inputfield->wrapAttr($attrName, $attrVal);}foreach($addClasses as $classKey => $class) {$inputfield->addClass($class, $classKey);}foreach($sets as $setName => $setVal) {$inputfield->set($setName, $setVal);}}/*** Render this Inputfield and the output of its children.** #pw-group-output** @todo this method has become too long/complex, move to its own pluggable class and split it up a lot* @return string**/public function ___render() {$sanitizer = $this->wire()->sanitizer;$out = '';$children = $this->preRenderChildren();$columnWidthTotal = 0;$columnWidthSpacing = $this->getSetting('columnWidthSpacing');$quietMode = $this->getSetting('quietMode');$lastInputfield = null;$_markup = array_merge(self::$defaultMarkup, self::$markup);$_classes = array_merge(self::$defaultClasses, self::$classes);$markup = array();$classes = array();$useColumnWidth = $this->useColumnWidth;$renderAjaxInputfield = $this->wire()->config->ajax ? $this->wire()->input->get('renderInputfieldAjax') : null;$lockedStates = array(Inputfield::collapsedNoLocked, Inputfield::collapsedYesLocked, Inputfield::collapsedBlankLocked);if($useColumnWidth === true && isset($_classes['form']) && strpos($_classes['form'], 'InputfieldFormNoWidths') !== false) {$useColumnWidth = false;}// show description for tabs$description = $quietMode ? '' : $this->getSetting('description');if($description && wireClassExists("InputfieldFieldsetTabOpen") && $this instanceof InputfieldFieldsetTabOpen) {$out .= str_replace('{out}', nl2br($this->entityEncode($description, true)), $_markup['item_head']);}foreach($children as $inputfield) {if($renderAjaxInputfield && $inputfield->attr('id') !== $renderAjaxInputfield&& !$inputfield instanceof InputfieldWrapper) {$skip = true;foreach($inputfield->getParents() as $parent) {/** @var InputfieldWrapper $parent */if($parent->attr('id') === $renderAjaxInputfield) $skip = false;}if($skip && !empty($parents)) continue;}list($markup, $classes) = array($_markup, $_classes);$this->attributeInputfield($inputfield, $markup, $classes);$renderValueMode = $this->getSetting('renderValueMode');$collapsed = (int) $inputfield->getSetting('collapsed');$required = $inputfield->getSetting('required');$requiredIf = $required ? $inputfield->getSetting('requiredIf') : false;$showIf = $inputfield->getSetting('showIf');if($collapsed == Inputfield::collapsedHidden) continue;if(in_array($collapsed, $lockedStates)) $renderValueMode = true;$ffOut = $this->renderInputfield($inputfield, $renderValueMode);if(!strlen($ffOut)) continue;$collapsed = (int) $inputfield->getSetting('collapsed'); // retrieve again after render$entityEncodeText = $inputfield->getSetting('entityEncodeText') === false ? false : true;$errorsOut = '';if(!$inputfield instanceof InputfieldWrapper) {$errors = $inputfield->getErrors(true);if(count($errors)) {$collapsed = $renderValueMode ? Inputfield::collapsedNoLocked : Inputfield::collapsedNo;$errorsOut = implode(', ', $errors);}} else $errors = array();foreach(array('error', 'description', 'head', 'notes', 'detail') as $property) {$text = $property == 'error' ? $errorsOut : $inputfield->getSetting($property);if($property === 'detail' && !is_string($text)) continue; // may not be necessaryif(!empty($text) && !$quietMode) {if($entityEncodeText) {$text = $inputfield->entityEncode($text, true);}if($inputfield->textFormat != Inputfield::textFormatMarkdown) {$text = str_replace('{out}', nl2br($text), $markup["item_$property"]);}} else {$text = '';}$_property = '{' . $property . '}';if(strpos($markup['item_content'], $_property) !== false) {$markup['item_content'] = str_replace($_property, $text, $markup['item_content']);} else if(strpos($markup['item_label'], $_property) !== false) {$markup['item_label'] = str_replace($_property, $text, $markup['item_label']);} else if($text && ($property == 'notes' || $property == 'detail')) {$ffOut .= $text;} else if($text) {$ffOut = $text . $ffOut;}}if(!$quietMode) {$prependMarkup = $inputfield->getSetting('prependMarkup');if($prependMarkup) $ffOut = $prependMarkup . $ffOut;$appendMarkup = $inputfield->getSetting('appendMarkup');if($appendMarkup) $ffOut .= $appendMarkup;}// The inputfield classname is always used in its wrapping element$ffAttrs = array('class' => str_replace(array('{class}', '{name}'),array($inputfield->className(), $inputfield->attr('name')),$classes['item']));if($inputfield instanceof InputfieldItemList) $ffAttrs['class'] .= " InputfieldItemList";if($collapsed) $ffAttrs['class'] .= " collapsed$collapsed";if(count($errors)) $ffAttrs['class'] .= ' ' . $classes['item_error'];if($required) $ffAttrs['class'] .= ' ' . $classes['item_required'];if(strlen($showIf) && !$this->getSetting('renderValueMode')) { // note: $this->renderValueMode (rather than $renderValueMode) is intentional$ffAttrs['data-show-if'] = $showIf;$ffAttrs['class'] .= ' ' . $classes['item_show_if'];}if(strlen($requiredIf)) {$ffAttrs['data-required-if'] = $requiredIf;$ffAttrs['class'] .= ' ' . $classes['item_required_if'];}if($collapsed && $collapsed !== Inputfield::collapsedNever) {$isEmpty = $inputfield->isEmpty();if(($isEmpty && $inputfield instanceof InputfieldWrapper && $collapsed !== Inputfield::collapsedPopulated) ||$collapsed === Inputfield::collapsedYes ||$collapsed === Inputfield::collapsedYesLocked ||$collapsed === true ||$collapsed === Inputfield::collapsedYesAjax ||($isEmpty && $collapsed === Inputfield::collapsedBlank) ||($isEmpty && $collapsed === Inputfield::collapsedBlankAjax) ||($isEmpty && $collapsed === Inputfield::collapsedBlankLocked) ||(!$isEmpty && $collapsed === Inputfield::collapsedPopulated)) {$ffAttrs['class'] .= ' ' . $classes['item_collapsed'];}}if($inputfield instanceof InputfieldWrapper) {// if the child is a wrapper, then id, title and class attributes become part of the LI wrapperforeach($inputfield->getAttributes() as $k => $v) {if(in_array($k, array('id', 'title', 'class'))) {$ffAttrs[$k] = isset($ffAttrs[$k]) ? $ffAttrs[$k] . " $v" : $v;}}}// if inputfield produced no output, then move to nextif(!strlen($ffOut)) continue;// wrap the inputfield output$attrs = '';$label = $inputfield->getSetting('label');$skipLabel = $inputfield->getSetting('skipLabel');$skipLabel = is_bool($skipLabel) || empty($skipLabel) ? (bool) $skipLabel : (int) $skipLabel; // force as bool or intif(!strlen($label) && $skipLabel !== Inputfield::skipLabelBlank && $inputfield->className() != 'InputfieldWrapper') {$label = $inputfield->attr('name');}if(($label || $quietMode) && $skipLabel !== Inputfield::skipLabelMarkup) {$for = $skipLabel || $quietMode ? '' : $inputfield->attr('id');// if $inputfield has a property of entityEncodeLabel with a value of boolean FALSE, we don't entity encode$entityEncodeLabel = $inputfield->getSetting('entityEncodeLabel');if(is_int($entityEncodeLabel) && $entityEncodeLabel >= Inputfield::textFormatBasic) {// uses an Inputfield::textFormat constant$label = $inputfield->entityEncode($label, $entityEncodeLabel);} else if($entityEncodeLabel !== false) {$label = $inputfield->entityEncode($label);}$icon = $inputfield->getSetting('icon');$icon = $icon ? str_replace('{name}', $sanitizer->name(str_replace(array('icon-', 'fa-'), '', $icon)), $markup['item_icon']) : '';$toggle = $collapsed == Inputfield::collapsedNever ? '' : $markup['item_toggle'];if($toggle && strpos($toggle, 'title=') === false) {$toggle = str_replace("class=", "title='" . $this->_('Toggle open/close') . "' class=", $toggle);}if($skipLabel === Inputfield::skipLabelHeader || $quietMode) {// label only shows when field is collapsed$label = str_replace('{out}', $icon . $label . $toggle, $markup['item_label_hidden']);} else {// label always visible$label = str_replace(array('{for}', '{out}'), array($for, $icon . $label . $toggle), $markup['item_label']);}$headerClass = trim($inputfield->getSetting('headerClass') . " $classes[item_label]");if($headerClass) {if(strpos($label, '{class}') !== false) {$label = str_replace('{class}', ' ' . $headerClass, $label);} else {$label = preg_replace('/( class=[\'"][^\'"]+)/', '$1 ' . $headerClass, $label, 1);}} else if(strpos($label, '{class}') !== false) {$label = str_replace('{class}', '', $label);}} else if($skipLabel === Inputfield::skipLabelMarkup) {// no header and no markup for header$label = '';} else {// no header// $inputfield->addClass('InputfieldNoHeader', 'wrapClass');}$columnWidth = (int) $inputfield->getSetting('columnWidth');$columnWidthAdjusted = $columnWidth;if($columnWidthSpacing) {$columnWidthAdjusted = $columnWidth + ($columnWidthTotal ? -1 * $columnWidthSpacing : 0);}if($columnWidth >= 9 && $columnWidth <= 100) {$ffAttrs['class'] .= ' ' . $classes['item_column_width'];if(!$columnWidthTotal) {$ffAttrs['class'] .= ' ' . $classes['item_column_width_first'];}$columnWidthTotal += $columnWidth;if(!$useColumnWidth || $useColumnWidth > 1) {if($columnWidthTotal >= 95 && $columnWidthTotal < 100) {$columnWidthAdjusted += (100 - $columnWidthTotal);$columnWidthTotal = 100;}$ffAttrs['data-colwidth'] = "$columnWidthAdjusted%";}if($useColumnWidth) {$ffAttrs['style'] = "width: $columnWidthAdjusted%;";}//if($columnWidthTotal >= 100 && !$requiredIf) $columnWidthTotal = 0; // requiredIf meant to be a showIf?if($columnWidthTotal >= 100) $columnWidthTotal = 0;} else {$columnWidthTotal = 0;}if(!isset($ffAttrs['id'])) $ffAttrs['id'] = 'wrap_' . $inputfield->attr('id');$ffAttrs['class'] = str_replace('Inputfield_ ', '', $ffAttrs['class']);$wrapClass = $inputfield->getSetting('wrapClass');if($wrapClass) $ffAttrs['class'] .= " " . $wrapClass;foreach($inputfield->wrapAttr() as $k => $v) {if(!empty($ffAttrs[$k])) {$ffAttrs[$k] .= " $v";} else {$ffAttrs[$k] = $v;}}foreach($ffAttrs as $k => $v) {$k = $this->entityEncode($k);$v = $this->entityEncode(trim($v));$attrs .= " $k='$v'";}$markupItemContent = $markup['item_content'];$contentClass = trim($inputfield->getSetting('contentClass') . " $classes[item_content]");if($contentClass) {if(strpos($markupItemContent, '{class}') !== false) {$markupItemContent = str_replace('{class}', ' ' . $contentClass, $markupItemContent);} else {$markupItemContent = preg_replace('/( class=[\'"][^\'"]+)/', '$1 ' . $contentClass, $markupItemContent, 1);}} else if(strpos($markupItemContent, '{class}') !== false) {$markupItemContent = str_replace('{class}', '', $markupItemContent);}if($inputfield->className() != 'InputfieldWrapper') $ffOut = str_replace('{out}', $ffOut, $markupItemContent);$out .= str_replace(array('{attrs}', '{out}'), array(trim($attrs), $label . $ffOut), $markup['item']);$lastInputfield = $inputfield;} // foreach($children as $inputfield)if($out) {$ulClass = $classes['list'];$lastColumnWidth = $lastInputfield ? $lastInputfield->getSetting('columnWidth') : 0;if($columnWidthTotal || ($lastInputfield && $lastColumnWidth >= 10 && $lastColumnWidth < 100)) {$ulClass .= ' ' . $classes['list_clearfix'];}$attrs = "class='$ulClass'"; // . ($this->attr('class') ? ' ' . $this->attr('class') : '') . "'";if(!($this instanceof InputfieldForm)) {foreach($this->getAttributes() as $attr => $value) {if(strpos($attr, 'data-') === 0) $attrs .= " $attr='" . $this->entityEncode($value) . "'";}}$out = $this->attr('value') . str_replace(array('{attrs}', '{out}'), array($attrs, $out), $markup['list']);}return $out;}/*** Render the output of this Inputfield and its children, showing values only (no inputs)** #pw-group-output** @return string**/public function ___renderValue() {if(!count($this->children())) return '';$this->addClass('InputfieldRenderValueMode');$this->set('renderValueMode', true);$out = $this->render();$this->set('renderValueMode', false);return $out;}/*** Render output for an individual Inputfield** This method takes care of all the pre-and-post requisites needed for rendering an Inputfield* among a group of Inputfields. It is used by the `InputfieldWrapper::render()` method for each* Inputfield present in the children.** #pw-group-output** @param Inputfield $inputfield The Inputfield to render.* @param bool $renderValueMode Specify true if we are only rendering values (default=false).* @return string Rendered output**/public function ___renderInputfield(Inputfield $inputfield, $renderValueMode = false) {$inputfieldID = $inputfield->attr('id');$collapsed = $inputfield->getSetting('collapsed');$ajaxInputfield = $collapsed == Inputfield::collapsedYesAjax || ($collapsed == Inputfield::collapsedBlankAjax && $inputfield->isEmpty());$ajaxHiddenInput = "<input type='hidden' name='processInputfieldAjax[]' value='$inputfieldID' />";$ajaxID = $this->wire()->config->ajax ? $this->wire()->input->get('renderInputfieldAjax') : '';$required = $inputfield->getSetting('required');if($ajaxInputfield && (($required && $inputfield->isEmpty()) || !$this->wire()->user->isLoggedin())) {// if an ajax field is empty, and is required, then we don't use ajax render mode// plus, we only allow ajax inputfields for logged-in users$ajaxInputfield = false;if($collapsed == Inputfield::collapsedYesAjax) $inputfield->collapsed = Inputfield::collapsedYes;if($collapsed == Inputfield::collapsedBlankAjax) $inputfield->collapsed = Inputfield::collapsedBlank;// indicate to next processInput that this field can be processed$inputfield->appendMarkup .= $ajaxHiddenInput;}$restoreValue = null; // value to restore, if we happen to modify it before render (renderValueMode only)if($renderValueMode) {$flags = $inputfield->getSetting('renderValueFlags');$inputfield->addClass('InputfieldRenderValueMode', 'wrapClass');if($flags & Inputfield::renderValueMinimal) {$inputfield->addClass('InputfieldRenderValueMinimal', 'wrapClass');}if($flags & Inputfield::renderValueFirst) {// render only first item value$inputfield->addClass('InputfieldRenderValueFirst', 'wrapClass');$value = $inputfield->attr('value');if(WireArray::iterable($value) && count($value) > 1) {$restoreValue = $value;if(is_array($value)) {$inputfield->attr('value', array_slice($value, 0, 1));} else if($value instanceof WireArray) {$inputfield->attr('value', $value->slice(0, 1));}}}}$inputfield->renderReady($this, $renderValueMode);if($ajaxInputfield) {if($ajaxID && $ajaxID === $inputfieldID) {// render ajax inputfield$editable = $inputfield->editable();if($renderValueMode || !$editable) {echo $inputfield->renderValue();} else {echo $inputfield->render();echo $ajaxHiddenInput;}exit;} else if($ajaxID && $ajaxID != $inputfieldID && $inputfield instanceof InputfieldWrapper &&$inputfield->getChildByName(str_replace('Inputfield_', '', $ajaxID))) {// nested ajax inputfield, within another ajax inputfield$in = $inputfield->getChildByName(str_replace('Inputfield_', '', $ajaxID));return $this->renderInputfield($in, $renderValueMode);} else {// do not render ajax inputfield now, instead render placeholderreturn $this->renderInputfieldAjaxPlaceholder($inputfield, $renderValueMode);}}if(!$renderValueMode && $inputfield->editable()) return $inputfield->render();// renderValueMode$out = $inputfield->renderValue();if(!is_null($restoreValue)) {$inputfield->attr('value', $restoreValue);$inputfield->resetTrackChanges();}if(is_null($out)) return '';if(!strlen($out) && !$inputfield instanceof InputfieldWrapper) $out = ' '; // prevent output from being skipped overreturn $out;}/*** Render a placeholder for an ajax-loaded Inputfield** @param Inputfield $inputfield* @param bool $renderValueMode* @return string**/protected function renderInputfieldAjaxPlaceholder(Inputfield $inputfield, $renderValueMode) {$input = $this->wire()->input;$sanitizer = $this->wire()->sanitizer;$inputfieldID = $inputfield->attr('id');$url = $input->url();$queryString = $input->queryString();if(strpos($queryString, 'renderInputfieldAjax=') !== false) {// in case nested ajax request$queryString = preg_replace('/&?renderInputfieldAjax=[^&]+/', '', $queryString);}$url .= $queryString ? "?$queryString&" : "?";$url .= "renderInputfieldAjax=$inputfieldID";$url = $sanitizer->entities($url);$out = "<div class='renderInputfieldAjax'><input type='hidden' value='$url' /></div>";if($inputfield instanceof InputfieldWrapper) {// load assets they will needforeach($inputfield->getAll() as $in) {$in->renderReady($inputfield, $renderValueMode);}}// ensure that Inputfield::render() hooks are still calledif($inputfield->hasHook('render()')) {$inputfield->runHooks('render', array(), 'before');}return $out;}/*** Process input for all children** #pw-group-input** @param WireInputData $input* @return $this**/public function ___processInput(WireInputData $input) {if(!$this->children) return $this;foreach($this->children() as $key => $child) {/** @var Inputfield $child */// skip over the field if it is not processableif(!$this->isProcessable($child)) continue;// pass along the dependencies value to child wrappersif($child instanceof InputfieldWrapper && $this->getSetting('useDependencies') === false) {$child->set('useDependencies', false);}// call the inputfield's processInput method$child->processInput($input);// check if a value is required and field is empty, trigger an error if soif($child->attr('name') && $child->getSetting('required') && $child->isEmpty()) {$requiredLabel = $child->getSetting('requiredLabel');if(empty($requiredLabel)) $requiredLabel = $this->requiredLabel;$child->error($requiredLabel);}}return $this;}/*** Is the given Inputfield processable for input?** Returns whether or not the given Inputfield should be processed by processInput()** When an `Inputfield` has a `showIf` property, then this returns false, but it queues* the field in the delayedChildren array for later processing. The root container should* temporarily remove the 'showIf' property of inputfields they want processed.** #pw-internal** @param Inputfield $inputfield* @return bool**/public function isProcessable(Inputfield $inputfield) {if(!$inputfield->editable()) return false;// visibility settings that aren't saveablestatic $skipTypes = array(Inputfield::collapsedHidden,Inputfield::collapsedLocked,Inputfield::collapsedNoLocked,Inputfield::collapsedBlankLocked,Inputfield::collapsedYesLocked);$collapsed = (int) $inputfield->getSetting('collapsed');if(in_array($collapsed, $skipTypes)) return false;if(in_array($collapsed, array(Inputfield::collapsedYesAjax, Inputfield::collapsedBlankAjax))) {$processAjax = $this->wire()->input->post('processInputfieldAjax');if(is_array($processAjax) && in_array($inputfield->attr('id'), $processAjax)) {// field can be processed (convention used by InputfieldWrapper)} else if($collapsed == Inputfield::collapsedBlankAjax && !$inputfield->isEmpty()) {// field can be processed because it is only collapsed if blank} else if(isset($_SERVER['HTTP_X_FIELDNAME']) && $_SERVER['HTTP_X_FIELDNAME'] === $inputfield->attr('name')) {// field can be processed (convention used by ajax uploaded file and other ajax types)} else {// field was not rendered via ajax and thus can't be processedreturn false;}unset($processAjax);}// if dependencies aren't in use, we can skip the restif($this->getSetting('useDependencies') === false) return true;if(strlen($inputfield->getSetting('showIf')) ||($inputfield->getSetting('required') && strlen($inputfield->getSetting('requiredIf')))) {$name = $inputfield->attr('name');if(!$name) {$name = $inputfield->attr('id');if(!$name) $name = $this->wire()->sanitizer->fieldName($inputfield->getSetting('label'));$inputfield->attr('name', $name);}$this->delayedChildren[$name] = $inputfield;return false;}return true;}/*** Returns true if all children are empty, or false if one or more is populated** #pw-group-retrieval-and-traversal** @return bool**/public function isEmpty() {$empty = true;foreach($this->children() as $child) {if(!$child->isEmpty()) {$empty = false;break;}}return $empty;}/*** Return Inputfields in this wrapper that are required and have empty values** This method includes all children up through the tree, not just direct children.** #pw-internal** @param bool $required Only include empty Inputfields that are required? (default=true)* @return array of Inputfield instances indexed by name attributes**/public function getEmpty($required = true) {$a = array();static $n = 0;foreach($this->children() as $child) {if($child instanceof InputfieldWrapper) {$a = array_merge($a, $child->getEmpty($required));} else {if($required && !$child->getSetting('required')) continue;if(!$child->isEmpty()) continue;$name = $child->attr('name');if(empty($name)) $name = "_unknown" . (++$n);$a[$name] = $child;}}return $a;}/*** Return an array of errors that occurred on any of the children during input processing.** Should only be called after `InputfieldWrapper::processInput()`.** #pw-group-input* #pw-group-retrieval-and-traversal** @param bool $clear Specify true to clear out the errors (default=false).* @return array Array of error strings**/public function getErrors($clear = false) {$errors = parent::getErrors($clear);foreach($this->children() as $key => $child) {foreach($child->getErrors($clear) as $e) {$label = $child->getSetting('label');$msg = $label ? $label : $child->attr('name');$errors[] = $msg . " - $e";}}return $errors;}/*** Return all children Inputfield objects** #pw-group-retrieval-and-traversal** @param string $selector Optional selector string to filter the children by* @return InputfieldsArray**/public function children($selector = '') {if($selector) {return $this->children->find($selector);} else {return $this->children;}}/*** Find an Inputfield below this one that has the given name** This is an alternative to the `getChildByName()` method, with more options for when you need it.* For instance, it can also accept a selector string or numeric index for the $name argument, and you* can optionally disable the $recursive behavior.** #pw-group-retrieval-and-traversal** @param string|int $name Name or selector string of child to find, omit for first child, or specify zero-based index of child to return.* @param bool $recursive Find child recursively? Looks for child in this wrapper, and all other wrappers below it. (default=true)* @return Inputfield|null Returns Inputfield instance if found, or null if not.* @since 3.0.110**/public function child($name = '', $recursive = true) {$child = null;$children = $this->children();if(!$children->count()) {// no child possible} else if(empty($name)) {// first child$child = $children->first();} else if(is_int($name)) {// number index$child = $children->eq($name);} else if($this->wire()->sanitizer->name($name) === $name) {// child by name$wrappers = array();foreach($children as $f) {if($f->getAttribute('name') === $name) {$child = $f;break;} else if($recursive && $f instanceof InputfieldWrapper) {$wrappers[] = $f;}}if(!$child && $recursive && count($wrappers)) {foreach($wrappers as $wrapper) {$child = $wrapper->child($name, $recursive);if($child) break;}}} else if(Selectors::stringHasSelector($name)) {// first child matching selector string$child = $children->find("$name, limit=1")->first();}return $child;}/*** Return all children Inputfields (alias of children method)** #pw-internal** @param string $selector Optional selector string to filter the children by* @return InputfieldsArray**/public function getChildren($selector = '') {return $this->children($selector);}/*** Return array of inputfields (indexed by name) of fields that had dependencies and were not processed** The results are to be handled by the root containing element (i.e. InputfieldForm).** #pw-internal** @param bool $clear Set to true in order to clear the delayed children list.* @return array|Inputfield[]**/public function _getDelayedChildren($clear = false) {$a = $this->delayedChildren;foreach($this->children() as $child) {if(!$child instanceof InputfieldWrapper) continue;$a = array_merge($a, $child->_getDelayedChildren($clear));}if($clear) $this->delayedChildren = array();return $a;}/*** Find all children Inputfields matching a selector string** #pw-group-retrieval-and-traversal** @param string $selector Required selector string to filter the children by* @return InputfieldsArray**/public function find($selector) {return $this->children()->find($selector);}/*** Given an Inputfield name, return the child Inputfield or NULL if not found.** This traverses all children recursively to find the requested Inputfield.** This is the same as the `InputfieldWrapper::get()` method except that it can* only return Inputfield or null, and has no crossover with other settings,* properties or API variables.** #pw-group-retrieval-and-traversal** @param string $name Name of Inputfield* @return Inputfield|InputfieldWrapper|null* @see InputfieldWrapper::get(), InputfieldWrapper::children()**/public function getChildByName($name) {return strlen($name) ? $this->getByAttr('name', $name) : null;}/*** Shorter alias of getChildByName()** #pw-group-retrieval-and-traversal** @param string $name* @return Inputfield|null* @since 3.0.172**/public function getByName($name) {return strlen($name) ? $this->getByAttr('name', $name) : null;}/*** Given an attribute name and value, return the first matching Inputfield or null if not found** This traverses all children recursively to find the requested Inputfield.** #pw-group-retrieval-and-traversal** @param string $attrName Attribute to match, such as 'id', 'name', 'value', etc.* @param string $attrValue Attribute value to match* @return Inputfield|null* @since 3.0.196**/public function getByAttr($attrName, $attrValue) {$inputfield = null;foreach($this->children() as $child) {if($child->getAttribute($attrName) === $attrValue) {$inputfield = $child;} else if($child instanceof InputfieldWrapper) {$inputfield = $child->getByAttr($attrName, $attrValue);}if($inputfield) break;}return $inputfield;}/*** Get value of Inputfield by name** This traverses all children recursively to find the requested Inputfield,* and get the value attribute from it. A value of null is returned if the* Inputfield cannot be found.** @param string $name* @return string|int|float|bool|array|object|null* @since 3.0.172**/public function getValueByName($name) {$inputfield = $this->getByName($name);return $inputfield ? $inputfield->val() : null;}/*** Enables foreach() of the children of this class** Per the InteratorAggregate interface, make the Inputfield children iterable.** #pw-group-retrieval-and-traversal** @return InputfieldsArray**/#[\ReturnTypeWillChange]public function getIterator() {return $this->children();}/*** Return the quantity of children present** #pw-group-retrieval-and-traversal** @return int**/#[\ReturnTypeWillChange]public function count() {return count($this->children());}/*** Get all Inputfields below this recursively in a flat InputfieldWrapper (children, and their children, etc.)** Note that all InputfieldWrapper instances are removed as a result (except for the containing InputfieldWrapper).** #pw-group-retrieval-and-traversal** @param array $options Options to modify behavior (3.0.169+)* - `withWrappers` (bool): Also include InputfieldWrapper objects? (default=false) 3.0.169+* @return InputfieldWrapper|InputfieldsArray**/public function getAll(array $options = array()) {/** @var InputfieldsArray $all */$all = $this->wire(new InputfieldsArray());foreach($this->children() as $child) {if($child instanceof InputfieldWrapper) {if(!empty($options['withWrappers'])) $all->add($child);foreach($child->getAll($options) as $c) {$all->add($c);}} else {$all->add($child);}}return $all;}/*** Start or stop tracking changes, applying the same to any children** #pw-internal** @param bool $trackChanges* @return Inputfield|InputfieldWrapper**/public function setTrackChanges($trackChanges = true) {$children = $this->children();if(count($children)) foreach($children as $child) $child->setTrackChanges($trackChanges);return parent::setTrackChanges($trackChanges);}/*** Start or stop tracking changes after clearing out any existing tracked changes, applying the same to any children** #pw-internal** @param bool $trackChanges* @return Inputfield|InputfieldWrapper**/public function resetTrackChanges($trackChanges = true) {$children = $this->children();if(count($children)) foreach($children as $child) $child->resetTrackChanges($trackChanges);return parent::resetTrackChanges($trackChanges);}/*** Get configuration Inputfields for this InputfieldWrapper** #pw-group-module** @return InputfieldWrapper**/public function ___getConfigInputfields() {$inputfields = parent::___getConfigInputfields();/** @var InputfieldSelect $f */$f = $inputfields->getChildByName('collapsed');if($f) {// remove all options for 'collapsed' except collapsedYes and collapsedNoforeach($f->getOptions() as $value => $label) {if(!in_array($value, array(Inputfield::collapsedNo, Inputfield::collapsedYes))) {$f->removeOption($value);}}}return $inputfields;}/*** Set custom markup for render, see self::$markup at top for reference.** #pw-internal** @param array $markup**/public static function setMarkup(array $markup) {self::$markup = array_merge(self::$markup, $markup);}/*** Get custom markup for render, see self::$markup at top for reference.** #pw-internal** @return array**/public static function getMarkup() {return array_merge(self::$defaultMarkup, self::$markup);}/*** Set custom classes for render, see self::$classes at top for reference.** #pw-internal** @param array $classes**/public static function setClasses(array $classes) {self::$classes = array_merge(self::$classes, $classes);}/*** Get custom classes for render, see self::$classes at top for reference.** #pw-internal** @return array**/public static function getClasses() {return array_merge(self::$defaultClasses, self::$classes);}/*** Import an array of Inputfield definitions to to this InputfieldWrapper instance** Your array should be an array of associative arrays, with each element describing an Inputfield.* The following properties are required for each Inputfield definition:** - `type` Which Inputfield module to use (may optionally exclude the "Inputfield" prefix).* - `name` Name attribute to use for the Inputfield.* - `label` Text label that appears above the Inputfield.** ~~~~~* // Example array for Inputfield definitions* array(* array(* 'name' => 'fullname',* 'type' => 'text',* 'label' => 'Field label'* 'columnWidth' => 50,* 'required' => true,* ),* array(* 'name' => 'color',* 'type' => 'select',* 'label' => 'Your favorite color',* 'description' => 'Select your favorite color or leave blank if you do not have one.',* 'columnWidth' => 50,* 'options' => array(* 'red' => 'Brilliant Red',* 'orange' => 'Citrus Orange',* 'blue' => 'Sky Blue'* )* ),* // alternative usage: associative array where name attribute is specified as key* 'my_fieldset' => array(* 'type' => 'fieldset',* 'label' => 'My Fieldset',* 'children' => array(* 'some_field' => array(* 'type' => 'text',* 'label' => 'Some Field',* )* )* );* // Note: you may alternatively use associative arrays where the keys are assumed to* // be the 'name' attribute.See the last item 'my_fieldset' above for an example.* ~~~~~** #pw-group-manipulation** @param array $a Array of Inputfield definitions* @param InputfieldWrapper $inputfields Specify the wrapper you want them added to, or omit to use current.* @return $this**/public function importArray(array $a, InputfieldWrapper $inputfields = null) {$modules = $this->wire()->modules;if(is_null($inputfields)) $inputfields = $this;if(!count($a)) return $inputfields;// if just a single field definition rather than an array of them, normalize to array of array$first = reset($a);if(!is_array($first)) $a = array($a);foreach($a as $name => $info) {if(isset($info['name'])) {$name = $info['name'];unset($info['name']);}if(!isset($info['type'])) {$this->error("Skipped field '$name' because no 'type' is set");continue;}$type = $info['type'];unset($info['type']);if(strpos($type, 'Inputfield') !== 0) $type = "Inputfield" . ucfirst($type);/** @var Inputfield $f */$f = $modules->get($type);if(!$f) {$this->error("Skipped field '$name' because module '$type' does not exist");continue;}$f->attr('name', $name);if($type === 'InputfieldCheckbox') {// checkbox behaves a little differently, just like in HTML/** @var InputfieldCheckbox $f */if(!empty($info['attr']['value'])) {$f->attr('value', $info['attr']['value']);} else if(!empty($info['value'])) {$f->attr('value', $info['value']);}unset($info['attr']['value'], $info['value']);$f->autocheck = 1; // future value attr set triggers checked state}if(isset($info['attr']) && is_array($info['attr'])) {foreach($info['attr'] as $key => $value) {$f->attr($key, $value);}unset($a['attr']);}foreach($info as $key => $value) {if($key == 'children') continue;$f->$key = $value;}if($f instanceof InputfieldWrapper && !empty($info['children'])) {$this->importArray($info['children'], $f);}$inputfields->add($f);}return $inputfields;}/*** Populate values for all Inputfields in this wrapper from the given $data object or array.** This iterates through every field in this InputfieldWrapper and looks for field names* that are also present in the given object or array. If present, it uses them to populate* the associated Inputfield.** If given an array, it should be an associative with the field 'name' as the keys and* the field 'value' as the array value, i.e. `['field_name' => 'field_value']`.** #pw-group-manipulation** @param WireData|Wire|ConfigurableModule|array $data* @return array Returns array of field names that were populated**/public function populateValues($data) {$populated = array();foreach($this->getAll() as $inputfield) {if($inputfield instanceof InputfieldWrapper) continue;$name = $inputfield->attr('name');if(!$name) continue;$value = null;if(is_array($data)) {// array$value = isset($data[$name]) ? $data[$name] : null;} else if($data instanceof WireData) {// WireData object$value = $data->data($name);} else if(is_object($data)) {// Wire or other object with __get() implemented$value = $data->$name;}if($value === null) continue;if($inputfield instanceof InputfieldCheckbox) $inputfield->autocheck = 1;$inputfield->attr('value', $value);$populated[$name] = $name;}return $populated;}/*** Get an array of all family below this (recursively) for debugging purposes** #pw-internal** @return array**/public function debugMap() {$a = array();foreach($this as $in) {$info = array('id' => $in->id,'name' => $in->name,'type' => $in->className(),);if($in instanceof InputfieldWrapper) {$info['children'] = $in->debugMap();}$a[] = $info;}return $a;}/*** Debug info** #pw-internal** @return array**/public function __debugInfo() {$info = parent::__debugInfo();$info['children'] = $this->debugMap();return $info;}}