diff --git a/wire/core/Fields.php b/wire/core/Fields.php index dcaa1157..53186a49 100644 --- a/wire/core/Fields.php +++ b/wire/core/Fields.php @@ -1047,5 +1047,29 @@ class Fields extends WireSaveableItems { */ public function ___changeTypeReady(Saveable $item, Fieldtype $fromType, Fieldtype $toType) { } + /** + * Get Fieldtypes compatible (for type change) with given Field + * + * #pw-internal + * + * @param Field $field + * @return array Array of Fieldtype objects indexed by class name + * @since 3.0.140 + * + */ + public function getCompatibleFieldtypes(Field $field) { + $fieldtype = $field->type; + if($fieldtype) { + // ask fieldtype what is compatible + $fieldtypes = $fieldtype->getCompatibleFieldtypes($field); + // ensure original is present + $fieldtypes->prepend($fieldtype); + } else { + // allow all + $fieldtypes = $this->wire('fieldtypes'); + } + return $fieldtypes; + } + } diff --git a/wire/core/Fieldtypes.php b/wire/core/Fieldtypes.php index 6981dd40..2a560f30 100644 --- a/wire/core/Fieldtypes.php +++ b/wire/core/Fieldtypes.php @@ -10,6 +10,7 @@ * * */ + class Fieldtypes extends WireArray { /** @@ -19,12 +20,21 @@ class Fieldtypes extends WireArray { protected $preloaded = false; /** - * Construct this Fieldtypes object and load all Fieldtype modules + * Is this the $fieldtypes API var? + * + * @var bool + * + */ + protected $isAPI = false; + + /** + * Construct the $fieldtypes API var (load all Fieldtype modules into it) * */ public function init() { - foreach($this->wire('modules') as $module) { - if(strpos($module->className(), 'Fieldtype') === 0) { + $this->isAPI = true; + foreach($this->wire('modules') as $name => $module) { + if(strpos($name, 'Fieldtype') === 0) { // if($module instanceof ModulePlaceholder) $module = $this->wire('modules')->get($module->className()); $this->add($module); } @@ -37,7 +47,7 @@ class Fieldtypes extends WireArray { */ protected function preload() { if($this->preloaded) return; - $debug = $this->wire('config')->debug; + $debug = $this->isAPI && $this->wire('config')->debug; if($debug) Debug::timer('Fieldtypes.preload'); foreach($this->data as $key => $module) { if($module instanceof ModulePlaceholder) { @@ -57,11 +67,13 @@ class Fieldtypes extends WireArray { * */ public function isValidItem($item) { - return $item instanceof Fieldtype || $item instanceof ModulePlaceholder; + if($item instanceof Fieldtype) return true; + if($item instanceof ModulePlaceholder && strpos($item->className(), 'Fieldtype') === 0) return true; + return false; } /** - * Per the WireArray interface, keys must be strings (field names) + * Per the WireArray interface, keys must be strings (fieldtype class names) * * @param string|int $key * @return bool @@ -123,7 +135,7 @@ class Fieldtypes extends WireArray { if($fieldtype instanceof ModulePlaceholder) { $fieldtype = $this->wire('modules')->get($fieldtype->className()); - $this->set($key, $fieldtype); + if($fieldtype) $this->set($key, $fieldtype); } return $fieldtype; diff --git a/wire/core/Inputfield.php b/wire/core/Inputfield.php index f47cc73c..a835379b 100644 --- a/wire/core/Inputfield.php +++ b/wire/core/Inputfield.php @@ -54,6 +54,7 @@ * @property string $label Primary label text that appears above the input. #pw-group-labels * @property string $description Optional description that appears under label to provide more detailed information. #pw-group-labels * @property string $notes Optional notes that appear under input area to provide additional notes. #pw-group-labels + * @property string $detail Optional text details that appear under notes. @since 3.0.140 #pw-group-labels * @property string $icon Optional font-awesome icon name to accompany label (excluding the "fa-") part). #pw-group-labels * @property string $requiredLabel Optional custom label to display when missing required value. @since 3.0.98 #pw-group-labels * @property string $head Optional text that appears below label but above description (only used by some Inputfields). #pw-internal @@ -341,24 +342,25 @@ abstract class Inputfield extends WireData implements Module { self::$numInstances++; - $this->set('label', ''); // primary clickable label - $this->set('description', ''); // descriptive copy, below label + $this->set('label', ''); // primary clickable label + $this->set('description', ''); // descriptive copy, below label $this->set('icon', ''); // optional icon name to accompany label - $this->set('notes', ''); // highlighted descriptive copy, below output of input field - $this->set('head', ''); // below label, above description - $this->set('required', 0); // set to 1 to make value required for this field + $this->set('notes', ''); // highlighted descriptive copy, below output of input field + $this->set('detail', ''); // text details that appear below notes + $this->set('head', ''); // below label, above description + $this->set('required', 0); // set to 1 to make value required for this field $this->set('requiredIf', ''); // optional conditions to make it required - $this->set('collapsed', ''); // see the collapsed* constants at top of class (use blank string for unset value) - $this->set('showIf', ''); // optional conditions selector - $this->set('columnWidth', ''); // percent width of the field. blank or 0 = 100. + $this->set('collapsed', ''); // see the collapsed* constants at top of class (use blank string for unset value) + $this->set('showIf', ''); // optional conditions selector + $this->set('columnWidth', ''); // percent width of the field. blank or 0 = 100. $this->set('skipLabel', self::skipLabelNo); // See the skipLabel constants $this->set('wrapClass', ''); // optional class to apply to the Inputfield wrapper (contains InputfieldHeader + InputfieldContent) $this->set('headerClass', ''); // optional class to apply to InputfieldHeader wrapper $this->set('contentClass', ''); // optional class to apply to InputfieldContent wrapper $this->set('textFormat', self::textFormatBasic); // format applied to description and notes $this->set('renderValueFlags', 0); // see renderValue* constants, applicable to renderValue mode only - $this->set('prependMarkup', ''); - $this->set('appendMarkup', ''); + $this->set('prependMarkup', ''); // markup to prepend to Inputfield output + $this->set('appendMarkup', ''); // markup to append to Inputfield output // default ID attribute if no 'id' attribute set $this->defaultID = $this->className() . self::$numInstances; diff --git a/wire/core/InputfieldWrapper.php b/wire/core/InputfieldWrapper.php index c9e07fbc..db8cc7ac 100644 --- a/wire/core/InputfieldWrapper.php +++ b/wire/core/InputfieldWrapper.php @@ -52,6 +52,7 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre 'item_description' => "
{out}
", 'item_head' => "{out}
", + 'item_detail' => "{out}
", 'item_icon' => " ", 'item_toggle' => "", // ALSO: @@ -497,8 +498,9 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre } } else $errors = array(); - foreach(array('error', 'description', 'head', 'notes') as $property) { + 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 necessary if(!empty($text) && !$quietMode) { if($entityEncodeText) { $text = $inputfield->entityEncode($text, true); @@ -514,7 +516,7 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre $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') { + } else if($text && ($property == 'notes' || $property == 'detail')) { $ffOut .= $text; } else if($text) { $ffOut = $text . $ffOut;