1020 lines
31 KiB
Text
1020 lines
31 KiB
Text
|
<?php namespace ProcessWire;
|
|||
|
|
|||
|
/**
|
|||
|
* Base for selection form inputs, which by default behaves as a regular <select>
|
|||
|
*
|
|||
|
* Serves as the base for Inputfields that provide selection of options (whether single or multi).
|
|||
|
* As a result, this class includes functionality for, and checks for both single-and-multi selection values.
|
|||
|
* Sublcasses will want to override the render method, but it's not necessary to override processInput().
|
|||
|
* Subclasses that select multiple values should implement the InputfieldHasArrayValue interface.
|
|||
|
*
|
|||
|
* ProcessWire 3.x, Copyright 2023 by Ryan Cramer
|
|||
|
* https://processwire.com
|
|||
|
*
|
|||
|
* @property string|int $defaultValue
|
|||
|
* @property array|string $options Get or set options, array of [value => label], or use options string.
|
|||
|
* @property array $optionAttributes
|
|||
|
* @property bool $valueAddOption If value attr set from API (only) that is not an option, add it as an option? (default=false) 3.0.171+
|
|||
|
*
|
|||
|
*/
|
|||
|
class InputfieldSelect extends Inputfield implements InputfieldHasSelectableOptions {
|
|||
|
|
|||
|
/**
|
|||
|
* Options specific to this Select
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $options = array();
|
|||
|
|
|||
|
/**
|
|||
|
* Attributes for options specific to this select (if applicable)
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $optionAttributes = array();
|
|||
|
|
|||
|
/**
|
|||
|
* Alternate language labels for options, array of [ languageID => [ optionValue => optionLabel ] ]
|
|||
|
*
|
|||
|
* @var array
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $optionLanguageLabels = array();
|
|||
|
|
|||
|
/**
|
|||
|
* Return information about this module
|
|||
|
*
|
|||
|
*/
|
|||
|
public static function getModuleInfo() {
|
|||
|
return array(
|
|||
|
'title' => __('Select', __FILE__), // Module Title
|
|||
|
'summary' => __('Selection of a single value from a select pulldown', __FILE__), // Module Summary
|
|||
|
'version' => 102,
|
|||
|
'permanent' => true,
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Construct
|
|||
|
*
|
|||
|
*/
|
|||
|
public function __construct() {
|
|||
|
parent::__construct();
|
|||
|
$this->set('defaultValue', '');
|
|||
|
$this->set('valueAddOption', false);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Add an option that may be selected
|
|||
|
*
|
|||
|
* If you want to add an optgroup, use the $value param as the label, and the label param as an array of options.
|
|||
|
* Note that optgroups may not be applicable to other Inputfields that descend from InputfieldSelect.
|
|||
|
*
|
|||
|
* @param string $value Value that the option submits (or label of optgroup, if specifying an optgroup)
|
|||
|
* @param string $label|array Optional label associated with the value (if null, value will be used as the label), or array of optgroup options [value=>label]
|
|||
|
* @param array $attributes Optional attributes to be associated with this option (i.e. a 'selected' attribute for an <option> tag)
|
|||
|
* @return $this
|
|||
|
*
|
|||
|
*/
|
|||
|
public function addOption($value, $label = null, array $attributes = null) {
|
|||
|
if(is_null($label) || (is_string($label) && !strlen($label))) $label = $value;
|
|||
|
if(isset($this->options[$value])) unset($this->options[$value]);
|
|||
|
$this->options[$value] = $label;
|
|||
|
if(!is_null($attributes)) $this->optionAttributes[$value] = $attributes;
|
|||
|
return $this;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Add selectable option with label, optionally for specific language
|
|||
|
*
|
|||
|
* @param string|int $value
|
|||
|
* @param string $label
|
|||
|
* @param Language|null $language
|
|||
|
* @return $this
|
|||
|
* @since 3.0.176
|
|||
|
*
|
|||
|
*/
|
|||
|
public function addOptionLabel($value, $label, $language = null) {
|
|||
|
$this->optionLanguageLabel($language, $value, $label);
|
|||
|
return $this;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Add multiple options at once
|
|||
|
*
|
|||
|
* @param array $options Array of options to add. It is assumed that array keys are the option value, and array
|
|||
|
* values are the option labels, unless overridden by the $assoc argument.
|
|||
|
* @param bool $assoc Is $options an associative array? (default=true). Specify false if $options is intended to be
|
|||
|
* a regular PHP array, where the array keys/indexes should be ignored, and option value will also be the label.
|
|||
|
* @return $this
|
|||
|
*
|
|||
|
*/
|
|||
|
public function addOptions(array $options, $assoc = true) {
|
|||
|
foreach($options as $k => $v) {
|
|||
|
if($assoc) {
|
|||
|
$this->addOption($k, $v);
|
|||
|
} else {
|
|||
|
$this->addOption($v);
|
|||
|
}
|
|||
|
}
|
|||
|
return $this;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Set/replace all options
|
|||
|
*
|
|||
|
* @param array $options Array of options to add. It is assumed that array keys are the option value, and array
|
|||
|
* values are the option labels, unless overridden by the $assoc argument.
|
|||
|
* @param bool $assoc Is $options an associative array? (default=true). Specify false if $options is intended to be
|
|||
|
* a regular PHP array, where the array keys/indexes should be ignored, and option value will also be the label.
|
|||
|
* @return $this
|
|||
|
*
|
|||
|
*/
|
|||
|
public function setOptions(array $options, $assoc = true) {
|
|||
|
$this->options = array();
|
|||
|
return $this->addOptions($options, $assoc);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Get or set label for given option value/key (default language)
|
|||
|
*
|
|||
|
* @param string|int $key Option value to get or set label for
|
|||
|
* @param string|null $label If setting label, specify label to set. If getting, then omit.
|
|||
|
* @return string|bool Returns boolean false if option not found, otherwise returns option label (string).
|
|||
|
* @since 3.0.134
|
|||
|
* @see InputfieldSelect::optionLanguageLabel()
|
|||
|
*
|
|||
|
*/
|
|||
|
public function optionLabel($key, $label = null) {
|
|||
|
$returnLabel = false;
|
|||
|
if(isset($this->options[$key])) {
|
|||
|
if($label !== null) $this->options[$key] = $label;
|
|||
|
$returnLabel = $this->options[$key];
|
|||
|
} else {
|
|||
|
foreach($this->options as $k => $v) {
|
|||
|
if(is_array($v) && isset($v[$key])) {
|
|||
|
// optgroup
|
|||
|
if($label !== null) $this->options[$k][$key] = $label;
|
|||
|
$returnLabel = $v[$key];
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
return $returnLabel;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Get or set alternative language label(s)
|
|||
|
*
|
|||
|
* @param Language|int|string $language Language object, id or name (required).
|
|||
|
* @param string|null|bool $key Option key/value to get/set label for,
|
|||
|
* OR omit to return all currently set option language labels for language,
|
|||
|
* OR boolean false to remove all language labels for this option value/key.
|
|||
|
* OR array of [ optionValue => optionLabel ] to add multiple option values for language.
|
|||
|
* @param $label|string|bool Translated label text to set,
|
|||
|
* OR omit to GET language label.
|
|||
|
* OR boolean false to remove.
|
|||
|
* @return string|array|Inputfield Return value depends on given arguments
|
|||
|
*
|
|||
|
*/
|
|||
|
public function optionLanguageLabel($language, $key = null, $label = null) {
|
|||
|
$languages = $this->wire()->languages;
|
|||
|
if(!$languages) return $this;
|
|||
|
if(is_string($language) && !ctype_digit("$language")) {
|
|||
|
$language = $languages->get($language);
|
|||
|
}
|
|||
|
$languageID = (int) "$language"; // converts Page or string to id
|
|||
|
if(!isset($this->optionLanguageLabels[$languageID])) {
|
|||
|
$this->optionLanguageLabels[$languageID] = array();
|
|||
|
}
|
|||
|
if($key === null) {
|
|||
|
return $this->optionLanguageLabels[$languageID];
|
|||
|
} else if($key === false) {
|
|||
|
unset($this->optionLanguageLabels[$languageID]);
|
|||
|
} else if(is_array($key)) {
|
|||
|
foreach($key as $k => $v) $this->optionLanguageLabels[$languageID][$k] = $v;
|
|||
|
} else if($label === null) {
|
|||
|
return isset($this->optionLanguageLabels[$languageID][$key]) ? $this->optionLanguageLabels[$languageID][$key] : '';
|
|||
|
} else if($label === false) {
|
|||
|
unset($this->optionLanguageLabels[$languageID][$key]);
|
|||
|
} else {
|
|||
|
$this->optionLanguageLabels[$languageID][$key] = $label;
|
|||
|
}
|
|||
|
return $this;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Given a multi-line string, convert it to options, one per line
|
|||
|
*
|
|||
|
* Lines preceded with a plus "+" are assumed selected, i.e. +option
|
|||
|
* Lines with an equals sign are split into separate value and label, i.e. value=label
|
|||
|
*
|
|||
|
* @param string $value
|
|||
|
* @return $this
|
|||
|
*
|
|||
|
*/
|
|||
|
public function addOptionsString($value) {
|
|||
|
|
|||
|
$value = (string) $value;
|
|||
|
$options = explode("\n", $value);
|
|||
|
$lastOption = '';
|
|||
|
$optgroup = array();
|
|||
|
$optgroupLabel = '';
|
|||
|
|
|||
|
foreach($options as $option) {
|
|||
|
|
|||
|
// in an optgroup when line starts with 3 or more spaces
|
|||
|
if(strpos($option, ' ') === 0 && $lastOption !== '') {
|
|||
|
// if no optgroupLabel, we're starting a new option group
|
|||
|
if(empty($optgroupLabel)) $optgroupLabel = $lastOption;
|
|||
|
$option = trim($option);
|
|||
|
} else {
|
|||
|
if($optgroupLabel) $this->addOption($optgroupLabel, $optgroup);
|
|||
|
$optgroup = array();
|
|||
|
$optgroupLabel = '';
|
|||
|
}
|
|||
|
|
|||
|
$option = trim($option);
|
|||
|
$attrs = array();
|
|||
|
$label = null;
|
|||
|
|
|||
|
if(strpos($option, '++') === 0) {
|
|||
|
// double plus should convert to single plus and not make it selected
|
|||
|
$option = substr($option, 1);
|
|||
|
} else if(substr($option, 0, 1) === '+') {
|
|||
|
// if option starts with a plus then make it selected
|
|||
|
$attrs['selected'] = 'selected';
|
|||
|
$option = ltrim($option, '+');
|
|||
|
} else if(strpos($option, 'disabled:') === 0) {
|
|||
|
// if option starts with "disabled:" then make it disabled
|
|||
|
$attrs['disabled'] = 'disabled';
|
|||
|
$option = preg_replace('/^disabled:\s*/', '', $option);
|
|||
|
}
|
|||
|
|
|||
|
if(strpos($option, '=') !== false && strpos($option, '==') === false) {
|
|||
|
// option has an equals "=", but not "==", then assume it's a: value=label
|
|||
|
list($option, $label) = explode('=', $option);
|
|||
|
}
|
|||
|
|
|||
|
if(strpos($option, '==') !== false) {
|
|||
|
// convert double equals "==" to single equals "=", as a means of allowing escaped equals sign
|
|||
|
$option = str_replace('==', '=', $option);
|
|||
|
}
|
|||
|
|
|||
|
$option = trim($option, '+ ');
|
|||
|
|
|||
|
if($optgroupLabel) {
|
|||
|
// add option to optgroup
|
|||
|
$optgroup[$option] = is_null($label) ? $option : $label;
|
|||
|
if(count($attrs)) $this->optionAttributes[$option] = $attrs;
|
|||
|
} else {
|
|||
|
// add the option
|
|||
|
$this->addOption($option, $label, $attrs);
|
|||
|
}
|
|||
|
|
|||
|
$lastOption = $option;
|
|||
|
}
|
|||
|
|
|||
|
if($optgroupLabel && count($optgroup)) {
|
|||
|
$this->addOption($optgroupLabel, $optgroup);
|
|||
|
}
|
|||
|
|
|||
|
return $this;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Add/modify existing option labels from a line separated key=value string, primarily for multi-language support
|
|||
|
*
|
|||
|
* @param string $str String of optionValue=optionLabel with each on its own line
|
|||
|
* @param int $languageID Language ID to set for, or omit for default language
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function addOptionLabelsString($str, $languageID = 0) {
|
|||
|
foreach(explode("\n", $str) as $line) {
|
|||
|
$line = trim($line);
|
|||
|
$line = ltrim($line, '+');
|
|||
|
if(strpos($line, 'disabled:') === 0) list(,$line) = explode('disabled:', $line, 2);
|
|||
|
if(strpos($line, '=') === false) continue;
|
|||
|
list($key, $label) = explode('=', $line, 2);
|
|||
|
if($languageID) {
|
|||
|
$this->optionLanguageLabel($languageID, $key, $label);
|
|||
|
} else {
|
|||
|
$this->addOption($key, $label);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Remove the option with the given value
|
|||
|
*
|
|||
|
* @param string|int $value
|
|||
|
* @return $this
|
|||
|
*
|
|||
|
*/
|
|||
|
public function removeOption($value) {
|
|||
|
unset($this->options[$value]);
|
|||
|
return $this;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Replace an option already present with the new value (and optionally new label and attributes)
|
|||
|
*
|
|||
|
* @param string|int|float $oldValue
|
|||
|
* @param string|int|float $newValue
|
|||
|
* @param string|null $newLabel Specify string to replace or omit (null) to leave existing label
|
|||
|
* @param array|null $newAttributes Specify array to replace, or omit (null) to leave existing attributes
|
|||
|
* @return bool True if option was replaced, false if oldValue was not found to replace,
|
|||
|
* @since 3.0.134
|
|||
|
*
|
|||
|
*/
|
|||
|
public function replaceOption($oldValue, $newValue, $newLabel = null, $newAttributes = null) {
|
|||
|
$options = array();
|
|||
|
$found = false;
|
|||
|
|
|||
|
foreach($this->options as $value => $label) {
|
|||
|
if($value === $oldValue) {
|
|||
|
$found = true;
|
|||
|
$options[$newValue] = ($newLabel === null ? $label : $newLabel);
|
|||
|
$attributes = is_array($newAttributes) ? $newAttributes : $this->getOptionAttributes($oldValue);
|
|||
|
unset($this->optionAttributes[$oldValue], $this->optionAttributes[$newValue]);
|
|||
|
if(!empty($attributes)) $this->setOptionAttributes($newValue, $attributes);
|
|||
|
} else {
|
|||
|
$options[$value] = $label;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if($found) $this->options = $options;
|
|||
|
|
|||
|
return $found;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Insert options before or after existing option
|
|||
|
*
|
|||
|
* @param array $options New options to insert [ value => label ]
|
|||
|
* @param string|int|null $existingValue Insert before or after option having this value
|
|||
|
* @param bool $insertAfter Insert after rather than before? (default=false)
|
|||
|
* @return self
|
|||
|
* @since 3.0.134
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function insertOptions(array $options, $existingValue = null, $insertAfter = false) {
|
|||
|
$a = array();
|
|||
|
|
|||
|
if($existingValue === null || !isset($this->options[$existingValue])) {
|
|||
|
// existing value isn’t present, so we will prepend or append instead
|
|||
|
if($insertAfter) {
|
|||
|
// append new options to end and return
|
|||
|
$this->addOptions($options);
|
|||
|
return $this;
|
|||
|
} else {
|
|||
|
// prepend to beginning
|
|||
|
$a = $options;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
foreach($this->options as $value => $label) {
|
|||
|
|
|||
|
if($value !== $existingValue) {
|
|||
|
if(!isset($a[$value])) $a[$value] = $label;
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
// if inserting after, new options will be inserted after this existing option
|
|||
|
if($insertAfter) $a[$value] = $label;
|
|||
|
|
|||
|
// add the new options
|
|||
|
foreach($options as $k => $v) {
|
|||
|
if(isset($a[$k])) unset($a[$k]);
|
|||
|
$a[$k] = $v;
|
|||
|
}
|
|||
|
|
|||
|
// add existing option back, after the new ones
|
|||
|
if(!$insertAfter && !isset($a[$value])) $a[$value] = $label;
|
|||
|
}
|
|||
|
|
|||
|
$this->options = $a;
|
|||
|
|
|||
|
return $this;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Insert new options before an existing option (or prepend options to beginning)
|
|||
|
*
|
|||
|
* @param array $options Associative array of `[ 'value' => 'label' ]` containing new options to add.
|
|||
|
* @param string|int|null $existingValue Existing option value to add options before, or omit to add at beginning.
|
|||
|
* @return self
|
|||
|
* @since 3.0.134
|
|||
|
*
|
|||
|
*/
|
|||
|
public function insertOptionsBefore(array $options, $existingValue = null) {
|
|||
|
return $this->insertOptions($options, $existingValue, false);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Insert new options after an existing option
|
|||
|
*
|
|||
|
* @param array $options Associative array of `[ 'value' => 'label' ]` containing new options to add.
|
|||
|
* @param string|int|null $existingValue Existing option value to add options after, or omit to append at end.
|
|||
|
* @return self
|
|||
|
* @since 3.0.134
|
|||
|
*
|
|||
|
*/
|
|||
|
public function insertOptionsAfter(array $options, $existingValue = null) {
|
|||
|
return $this->insertOptions($options, $existingValue, true);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Get all options for this Select
|
|||
|
*
|
|||
|
* @return array
|
|||
|
*
|
|||
|
*/
|
|||
|
public function getOptions() {
|
|||
|
return $this->options;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Returns whether the provided value is one of the available options
|
|||
|
*
|
|||
|
* @param string|int $value
|
|||
|
* @param array $options Array of options to check, or omit if using this classes options.
|
|||
|
* @return bool
|
|||
|
*
|
|||
|
*/
|
|||
|
public function isOption($value, array $options = null) {
|
|||
|
|
|||
|
if(is_null($options)) $options = $this->options;
|
|||
|
$is = false;
|
|||
|
|
|||
|
foreach($options as $key => $option) {
|
|||
|
if(is_array($option)) {
|
|||
|
// fieldgroup
|
|||
|
if($this->isOption($value, $option)) {
|
|||
|
$is = true;
|
|||
|
break;
|
|||
|
}
|
|||
|
} else {
|
|||
|
if("$value" === "$key") {
|
|||
|
$is = true;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return $is;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Returns whether the provided value is selected
|
|||
|
*
|
|||
|
* @param string|int $value
|
|||
|
* @return bool
|
|||
|
*
|
|||
|
*/
|
|||
|
public function isOptionSelected($value) {
|
|||
|
|
|||
|
$valueAttr = $this->attr('value');
|
|||
|
if($this->isEmpty()) {
|
|||
|
// no value set yet, check if it's set in any of the option attributes
|
|||
|
$selected = false;
|
|||
|
if(isset($this->optionAttributes[$value])) {
|
|||
|
$attrs = $this->optionAttributes[$value];
|
|||
|
if(!empty($attrs['selected']) || !empty($attrs['checked'])) $selected = true;
|
|||
|
|
|||
|
}
|
|||
|
if($selected) return true;
|
|||
|
}
|
|||
|
|
|||
|
if($this instanceof InputfieldHasArrayValue) {
|
|||
|
// multiple selection
|
|||
|
$selected = false;
|
|||
|
foreach($valueAttr as $v) {
|
|||
|
$selected = "$v" === "$value";
|
|||
|
if($selected) break;
|
|||
|
}
|
|||
|
return $selected;
|
|||
|
}
|
|||
|
|
|||
|
return "$value" == (string) $this->value;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Is the given option value disabled?
|
|||
|
*
|
|||
|
* @param $value
|
|||
|
* @return bool
|
|||
|
*
|
|||
|
*/
|
|||
|
public function isOptionDisabled($value) {
|
|||
|
$disabled = false;
|
|||
|
if(isset($this->optionAttributes[$value])) {
|
|||
|
$attrs = $this->optionAttributes[$value];
|
|||
|
if(!empty($attrs['disabled'])) $disabled = true;
|
|||
|
}
|
|||
|
return $disabled;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Get or set option attributes
|
|||
|
*
|
|||
|
* This method is a combined getOptionAttributes() and setOptionAttributes(). Use the dedicated get/set
|
|||
|
* methods when you need more options.
|
|||
|
*
|
|||
|
* @param string|int $key Option value to get or set attributes for, or omit to get all option attributes.
|
|||
|
* @param array|null|bool $attributes Specify array to set attributes, omit to get attributes
|
|||
|
* @param bool $append Specify true to append to existing attributes rather than replacing
|
|||
|
* @return array Associative array of attributes
|
|||
|
* @since 3.0.134
|
|||
|
*
|
|||
|
*/
|
|||
|
public function optionAttributes($key = null, $attributes = null, $append = false) {
|
|||
|
if($key === null) return $this->optionAttributes;
|
|||
|
if(is_array($attributes)) {
|
|||
|
if($append) {
|
|||
|
$this->addOptionAttributes($key, $attributes);
|
|||
|
} else {
|
|||
|
$this->setOptionAttributes($key, $attributes);
|
|||
|
}
|
|||
|
}
|
|||
|
return $this->getOptionAttributes($key);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Get an attributes array intended for an item (or for all items)
|
|||
|
*
|
|||
|
* @param string|int|null $key Option value, or omit to return ALL option attributes indexed by option value
|
|||
|
* @return array Array of attributes
|
|||
|
*
|
|||
|
*/
|
|||
|
public function getOptionAttributes($key = null) {
|
|||
|
if($key === null) return $this->optionAttributes;
|
|||
|
if(!isset($this->optionAttributes[$key])) return array();
|
|||
|
return $this->optionAttributes[$key];
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Set/replace entire attributes array for an item
|
|||
|
*
|
|||
|
* @param string|int|array $key Option value, or specify associative array (indexed by option value) to set ALL option attributes
|
|||
|
* @param array $attrs Array of attributes to set, or omit if you specified array for first argument.
|
|||
|
* @return $this
|
|||
|
*
|
|||
|
*/
|
|||
|
public function setOptionAttributes($key, array $attrs = array()) {
|
|||
|
if(is_array($key)) {
|
|||
|
$this->optionAttributes = $key;
|
|||
|
} else {
|
|||
|
$this->optionAttributes[$key] = $attrs;
|
|||
|
}
|
|||
|
return $this;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Add attributes for an item (without removing existing attributes), or for multiple items
|
|||
|
*
|
|||
|
* @param string|int|array $key Option value, or array of option attributes indexed by option value.
|
|||
|
* @param array $attrs Array of attributes to set, or omit if you specified array for first argument.
|
|||
|
* @return $this
|
|||
|
*
|
|||
|
*/
|
|||
|
public function addOptionAttributes($key, array $attrs = array()) {
|
|||
|
if(is_array($key)) {
|
|||
|
foreach($key as $k => $v) {
|
|||
|
$this->addOptionAttributes($k, $v);
|
|||
|
}
|
|||
|
} else {
|
|||
|
$value = isset($this->optionAttributes[$key]) ? $this->optionAttributes[$key] : array();
|
|||
|
$this->optionAttributes[$key] = array_merge($value, $attrs);
|
|||
|
}
|
|||
|
return $this;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Get an attributes string intended for the <option> element
|
|||
|
*
|
|||
|
* @param string|array $key If given an array, it will be assumed to the attributes you want rendered.
|
|||
|
* If given a value for an existing option, then the attributes for that option will be rendered.
|
|||
|
* @return string
|
|||
|
*
|
|||
|
*/
|
|||
|
public function getOptionAttributesString($key) {
|
|||
|
if(is_array($key)) {
|
|||
|
$attrs = $key;
|
|||
|
} else if(!isset($this->optionAttributes[$key])) {
|
|||
|
return '';
|
|||
|
} else {
|
|||
|
$attrs = $this->optionAttributes[$key];
|
|||
|
}
|
|||
|
return $this->getAttributesString($attrs);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Render the given options
|
|||
|
*
|
|||
|
* Note: method was protected prior to 3.0.116
|
|||
|
*
|
|||
|
* @param array|null $options Options array or omit (null) to use already specified options
|
|||
|
* @param bool $allowBlank Allow first item to be blank when supported? (default=true)
|
|||
|
* @return string
|
|||
|
*
|
|||
|
*/
|
|||
|
public function renderOptions($options = null, $allowBlank = true) {
|
|||
|
|
|||
|
if($options === null) $options = $this->options;
|
|||
|
$out = '';
|
|||
|
reset($options);
|
|||
|
$key = key($options);
|
|||
|
$hasBlankOption = empty($key);
|
|||
|
if($allowBlank && !$hasBlankOption && !$this->attr('multiple')) {
|
|||
|
if($this->getSetting('required') && $this->attr('value')) {
|
|||
|
// if required and a value is already selected, do not add a blank option
|
|||
|
} else {
|
|||
|
$out .= "<option value=''> </option>";
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
foreach($options as $value => $label) {
|
|||
|
|
|||
|
if(is_array($label)) {
|
|||
|
$out .=
|
|||
|
"<optgroup label='" . htmlspecialchars($value, ENT_QUOTES, 'UTF-8') . "'>" .
|
|||
|
$this->renderOptions($label, false) .
|
|||
|
"</optgroup>";
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
$selected = $this->isOptionSelected($value) ? " selected='selected'" : '';
|
|||
|
$attrs = $this->getOptionAttributes($value);
|
|||
|
unset($attrs['selected'], $attrs['checked'], $attrs['value']);
|
|||
|
$attrs = $this->getOptionAttributesString($attrs);
|
|||
|
$out .=
|
|||
|
"<option$selected $attrs value='" . htmlspecialchars($value, ENT_QUOTES, "UTF-8") . "'>" .
|
|||
|
$this->entityEncode($label) .
|
|||
|
"</option>";
|
|||
|
}
|
|||
|
|
|||
|
return $out;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Check for default value and populate when appropriate
|
|||
|
*
|
|||
|
* This should be called at the beginning of render() and at the end of processInput()
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function checkDefaultValue() {
|
|||
|
|
|||
|
if(!$this->required || !$this->defaultValue || !$this->isEmpty()) return;
|
|||
|
|
|||
|
// when a value is required and the value is empty and a default value is specified, we use it.
|
|||
|
if($this instanceof InputfieldHasArrayValue) {
|
|||
|
/** @var InputfieldSelect $this */
|
|||
|
$value = explode("\n", $this->defaultValue);
|
|||
|
foreach($value as $k => $v) {
|
|||
|
$value[$k] = trim($v); // remove possible extra LF
|
|||
|
}
|
|||
|
} else {
|
|||
|
$value = $this->defaultValue;
|
|||
|
$pos = strpos($value, "\n");
|
|||
|
if($pos) $value = substr($value, 0, $pos);
|
|||
|
$value = trim($value);
|
|||
|
}
|
|||
|
$this->attr('value', $value);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Render ready
|
|||
|
*
|
|||
|
* @param Inputfield|null $parent
|
|||
|
* @param bool $renderValueMode
|
|||
|
* @return bool
|
|||
|
*
|
|||
|
*/
|
|||
|
public function renderReady(Inputfield $parent = null, $renderValueMode = false) {
|
|||
|
if(!empty($this->optionLanguageLabels) && $this->hasFieldtype === false) {
|
|||
|
$languages = $this->wire()->languages;
|
|||
|
if($languages) {
|
|||
|
// make option labels use use language where available
|
|||
|
$language = $this->wire()->user->language;
|
|||
|
$defaultLanguage = $languages->getDefault();
|
|||
|
if(!empty($this->optionLanguageLabels[$language->id])) {
|
|||
|
$labels = $this->optionLanguageLabels[$language->id];
|
|||
|
foreach($this->options as $key => $defaultLabel) {
|
|||
|
if(empty($labels[$key])) continue;
|
|||
|
$this->options[$key] = $labels[$key];
|
|||
|
if($language->id != $defaultLanguage->id) {
|
|||
|
$this->optionLanguageLabel($defaultLanguage, $key, $defaultLabel);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
return parent::renderReady($parent, $renderValueMode);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Render and return the output for this Select
|
|||
|
*
|
|||
|
* @return string
|
|||
|
*
|
|||
|
*/
|
|||
|
public function ___render() {
|
|||
|
$this->checkDefaultValue();
|
|||
|
$attrs = $this->getAttributes();
|
|||
|
unset($attrs['value']);
|
|||
|
|
|||
|
return
|
|||
|
"<select " . $this->getAttributesString($attrs) . ">" .
|
|||
|
$this->renderOptions($this->options) .
|
|||
|
"</select>";
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Render non-editable value
|
|||
|
*
|
|||
|
* @return string
|
|||
|
*
|
|||
|
*/
|
|||
|
public function ___renderValue() {
|
|||
|
|
|||
|
$out = '';
|
|||
|
$sanitizer = $this->wire()->sanitizer;
|
|||
|
|
|||
|
foreach($this->options as $value => $label) {
|
|||
|
|
|||
|
$o = '';
|
|||
|
|
|||
|
if(is_array($label)) {
|
|||
|
foreach($label as $k => $v) {
|
|||
|
if($this->isOptionSelected($k)) {
|
|||
|
$o = trim($value, ' :') . ": $v";
|
|||
|
}
|
|||
|
}
|
|||
|
} else {
|
|||
|
if($this->isOptionSelected($value)) $o = $label;
|
|||
|
}
|
|||
|
|
|||
|
if(strlen($o)) {
|
|||
|
$out .= "<li>" . $sanitizer->entities($o) . "</li>";
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if(strlen($out)) {
|
|||
|
$out = "<ul class='pw-bullets'>$out</ul>";
|
|||
|
}
|
|||
|
|
|||
|
return $out;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Process input from the provided array
|
|||
|
*
|
|||
|
* In this case we're having the Inputfield base process the input and we're going back and validating the value.
|
|||
|
* If the value(s) that were set aren't in our specific list of options, we remove them. This is a security measure.
|
|||
|
*
|
|||
|
* @param WireInputData $input
|
|||
|
* @return $this
|
|||
|
*
|
|||
|
*/
|
|||
|
public function ___processInput(WireInputData $input) {
|
|||
|
|
|||
|
// disable valueAddOption temporarily to prevent it from applying to user input
|
|||
|
$valueAddOption = $this->valueAddOption;
|
|||
|
if($valueAddOption) $this->valueAddOption = false;
|
|||
|
|
|||
|
parent::___processInput($input);
|
|||
|
|
|||
|
$name = $this->attr('name');
|
|||
|
if(!isset($input[$name])) {
|
|||
|
$value = $this instanceof InputfieldHasArrayValue ? array() : null;
|
|||
|
$this->setAttribute('value', $value);
|
|||
|
return $this;
|
|||
|
}
|
|||
|
|
|||
|
// validate that the selected posted option(s) are those from our options list
|
|||
|
// removing any that aren't
|
|||
|
|
|||
|
$value = $this->attr('value');
|
|||
|
|
|||
|
if($this instanceof InputfieldHasArrayValue) {
|
|||
|
/** @var InputfieldSelect $this */
|
|||
|
if(!is_array($value)) $value = array();
|
|||
|
foreach($value as $k => $v) {
|
|||
|
if(!$this->isOption($v)) {
|
|||
|
unset($value[$k]); // remove invalid option
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
} else if($value && !$this->isOption($value)) {
|
|||
|
$value = null;
|
|||
|
}
|
|||
|
|
|||
|
$this->setAttribute('value', $value);
|
|||
|
$this->checkDefaultValue();
|
|||
|
if($valueAddOption) $this->valueAddOption = $valueAddOption;
|
|||
|
|
|||
|
return $this;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Get property
|
|||
|
*
|
|||
|
* @param string $key
|
|||
|
* @return array|mixed|null
|
|||
|
*
|
|||
|
*/
|
|||
|
public function get($key) {
|
|||
|
if($key === 'options') return $this->options;
|
|||
|
if($key === 'optionAttributes') return $this->optionAttributes;
|
|||
|
return parent::get($key);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Set property
|
|||
|
*
|
|||
|
* @param string $key
|
|||
|
* @param mixed $value
|
|||
|
* @return Inputfield|InputfieldSelect
|
|||
|
*
|
|||
|
*/
|
|||
|
public function set($key, $value) {
|
|||
|
|
|||
|
if($key == 'options') {
|
|||
|
if(is_string($value)) {
|
|||
|
return $this->addOptionsString($value);
|
|||
|
} else if(is_array($value)) {
|
|||
|
$this->options = $value;
|
|||
|
}
|
|||
|
return $this;
|
|||
|
} else if(strpos($key, 'options') === 0 && $this->hasFieldtype === false) {
|
|||
|
list(,$languageID) = explode('options', $key);
|
|||
|
if(ctype_digit($languageID)) {
|
|||
|
$this->addOptionLabelsString($value, (int) $languageID);
|
|||
|
return $this;
|
|||
|
}
|
|||
|
} else if($key == 'optionAttributes') {
|
|||
|
if(is_array($value)) {
|
|||
|
$this->optionAttributes = $value;
|
|||
|
}
|
|||
|
return $this;
|
|||
|
}
|
|||
|
|
|||
|
return parent::set($key, $value);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Set attribute
|
|||
|
*
|
|||
|
* @param array|string $key
|
|||
|
* @param array|int|string $value
|
|||
|
* @return Inputfield|InputfieldSelect
|
|||
|
*
|
|||
|
*/
|
|||
|
public function setAttribute($key, $value) {
|
|||
|
if($key === 'value') {
|
|||
|
if(is_object($value) || (is_string($value) && strpos($value, '|'))) {
|
|||
|
$value = (string) $value;
|
|||
|
if($this instanceof InputfieldHasArrayValue) {
|
|||
|
$value = explode('|', $value);
|
|||
|
}
|
|||
|
} else if(is_array($value)) {
|
|||
|
if($this instanceof InputfieldHasArrayValue) {
|
|||
|
// ok
|
|||
|
} else {
|
|||
|
$value = reset($value);
|
|||
|
}
|
|||
|
}
|
|||
|
if($this->valueAddOption) {
|
|||
|
// add option(s) for any value set from API
|
|||
|
if(is_array($value)) {
|
|||
|
foreach($value as $v) {
|
|||
|
if(!$this->isOption($v)) {
|
|||
|
if(strlen($v)) $this->addOption($v);
|
|||
|
}
|
|||
|
}
|
|||
|
} else {
|
|||
|
if(strlen("$value") && !$this->isOption($value)) {
|
|||
|
$this->addOption($value);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
return parent::setAttribute($key, $value);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Is the value empty?
|
|||
|
*
|
|||
|
* @return bool
|
|||
|
*
|
|||
|
*/
|
|||
|
public function isEmpty() {
|
|||
|
/** @var array|null|bool|string|int $value */
|
|||
|
$value = $this->attr('value');
|
|||
|
|
|||
|
if(is_array($value)) {
|
|||
|
$cnt = count($value);
|
|||
|
if(!$cnt) return true;
|
|||
|
if($cnt === 1) return strlen((string) reset($value)) === 0;
|
|||
|
return false; // $cnt > 1
|
|||
|
|
|||
|
} else if($value === null || $value === false) {
|
|||
|
return true;
|
|||
|
|
|||
|
} else if("$value" === "0") {
|
|||
|
if(!array_key_exists("$value", $this->options)) return true;
|
|||
|
|
|||
|
} else {
|
|||
|
return strlen("$value") === 0;
|
|||
|
}
|
|||
|
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Field configuration
|
|||
|
*
|
|||
|
* @return InputfieldWrapper
|
|||
|
*
|
|||
|
*/
|
|||
|
public function ___getConfigInputfields() {
|
|||
|
|
|||
|
$inputfields = parent::___getConfigInputfields();
|
|||
|
$modules = $this->wire()->modules;
|
|||
|
|
|||
|
if($this instanceof InputfieldHasArrayValue) {
|
|||
|
/** @var InputfieldTextarea $f */
|
|||
|
$f = $modules->get('InputfieldTextarea');
|
|||
|
$f->description = $this->_('To have pre-selected default value(s), enter the option values (one per line) below.');
|
|||
|
} else {
|
|||
|
/** @var InputfieldText $f */
|
|||
|
$f = $modules->get('InputfieldText');
|
|||
|
$f->description = $this->_('To have a pre-selected default value, enter the option value below.');
|
|||
|
}
|
|||
|
$f->attr('name', 'defaultValue');
|
|||
|
$f->label = $this->_('Default value');
|
|||
|
$f->attr('value', $this->defaultValue);
|
|||
|
$f->description .= ' ' . $this->_('For default page selection, the value would be the page ID number.');
|
|||
|
$f->notes = $this->_('IMPORTANT: The default value is not used unless the field is required (see the “required” checkbox on this screen).');
|
|||
|
$f->collapsed = $this->hasFieldtype === false ? Inputfield::collapsedBlank : Inputfield::collapsedNo;
|
|||
|
|
|||
|
$inputfields->add($f);
|
|||
|
|
|||
|
// if dealing with an inputfield that has an associated fieldtype,
|
|||
|
// we don't need to perform the remaining configuration
|
|||
|
if($this->hasFieldtype !== false) return $inputfields;
|
|||
|
|
|||
|
// the following configuration specific to non-Fieldtype use of single/multi-selects
|
|||
|
$isInputfieldSelect = $this->className() == 'InputfieldSelect';
|
|||
|
$languages = $this->wire()->languages;
|
|||
|
|
|||
|
/** @var InputfieldTextarea $f */
|
|||
|
$f = $modules->get('InputfieldTextarea');
|
|||
|
$f->attr('name', 'options');
|
|||
|
$f->label = $this->_('Options');
|
|||
|
$value = '';
|
|||
|
foreach($this->options as $key => $option) {
|
|||
|
if(is_array($option)) {
|
|||
|
$value .= "$key\n";
|
|||
|
foreach($option as $o) {
|
|||
|
$value .= " $o\n";
|
|||
|
}
|
|||
|
} else {
|
|||
|
$value .= "$option\n";
|
|||
|
}
|
|||
|
}
|
|||
|
$value = trim($value);
|
|||
|
if(empty($value)) {
|
|||
|
$optionLabel = $f->label;
|
|||
|
if($optionLabel === 'Options') $optionLabel = 'Option';
|
|||
|
$value = "=\n$optionLabel 1\n$optionLabel 2\n$optionLabel 3";
|
|||
|
if(!$isInputfieldSelect) $value = ltrim($value, '=');
|
|||
|
}
|
|||
|
$f->attr('value', $value);
|
|||
|
$f->attr('rows', 10);
|
|||
|
$f->description = $this->_('Enter the options that may be selected, one per line.');
|
|||
|
if($languages) $f->description .= ' ' . $this->_('To use multi-language option labels, please see the instructions below this field.');
|
|||
|
$f->notes =
|
|||
|
($languages ? '**' . $this->_('Instructions:') . "**\n" : '') .
|
|||
|
'• ' . $this->_('Specify one option per line.') . "\n" .
|
|||
|
'• ' . $this->_('To keep a separate value and label, separate them with an equals sign. Example: value=My Option') . " \n" .
|
|||
|
($isInputfieldSelect ? '• ' . $this->_('To precede your list with a blank option, enter just a equals sign "=" as the first option.') . "\n" : '') .
|
|||
|
'• ' . $this->_('To make an option selected, precede it with a plus sign. Example: +My Option') .
|
|||
|
($isInputfieldSelect ? "\n• " . $this->_('To create an optgroup (option group) indent the options in the group with 3 or more spaces.') : '');
|
|||
|
if($languages) $f->notes .= " \n\n**" . $this->_('Multi-language instructions:') . "**\n" .
|
|||
|
'• ' . $this->_('We recommend using using `value=label`, where `value` is the same across languages and `label` is translated.') . " \n" .
|
|||
|
'• ' . $this->_('First define your default language options, and then copy/paste into the other languages and translate labels.') . " \n" .
|
|||
|
'• ' . $this->_('Selected options and optgroups are defined on the default language; the other inputs are only for label translation.') . "\n" .
|
|||
|
'• ' . $this->_('Labels that are not translated inherit the default language label.');
|
|||
|
|
|||
|
if($languages) {
|
|||
|
$f->useLanguages = true;
|
|||
|
}
|
|||
|
|
|||
|
$inputfields->add($f);
|
|||
|
|
|||
|
return $inputfields;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
}
|