silverstripe-framework/forms/SelectField.php

255 lines
5.9 KiB
PHP

<?php
use SilverStripe\ORM\SS_List;
use SilverStripe\ORM\SS_Map;
/**
* Represents a field that allows users to select one or more items from a list
*/
abstract class SelectField extends FormField {
/**
* Associative or numeric array of all dropdown items,
* with array key as the submitted field value, and the array value as a
* natural language description shown in the interface element.
*
* @var array|ArrayAccess
*/
protected $source;
/**
* The values for items that should be disabled (greyed out) in the dropdown.
* This is a non-associative array
*
* @var array
*/
protected $disabledItems = array();
/**
* @param string $name The field name
* @param string $title The field title
* @param array|ArrayAccess $source A map of the dropdown items
* @param mixed $value The current value
*/
public function __construct($name, $title = null, $source = array(), $value = null) {
$this->setSource($source);
if(!isset($title)) {
$title = $name;
}
parent::__construct($name, $title, $value);
}
public function getSchemaStateDefaults() {
$data = parent::getSchemaStateDefaults();
$disabled = $this->getDisabledItems();
// Add options to 'data'
$source = $this->getSource();
$data['source'] = (is_array($source))
? array_map(function ($value, $title) use ($disabled) {
return [
'value' => $value,
'title' => $title,
'disabled' => in_array($value, $disabled),
];
}, array_keys($source), $source)
: [];
return $data;
}
/**
* Mark certain elements as disabled,
* regardless of the {@link setDisabled()} settings.
*
* These should be items that appear in the source list, not in addition to them.
*
* @param array|SS_List $items Collection of values or items
* @return $this
*/
public function setDisabledItems($items){
$this->disabledItems = $this->getListValues($items);
return $this;
}
/**
* Non-associative list of disabled item values
*
* @return array
*/
public function getDisabledItems(){
return $this->disabledItems;
}
/**
* Check if the given value is disabled
*
* @param string $value
* @return bool
*/
protected function isDisabledValue($value) {
if($this->isDisabled()) {
return true;
}
return in_array($value, $this->getDisabledItems());
}
public function getAttributes() {
return array_merge(
parent::getAttributes(),
array('type' => null, 'value' => null)
);
}
/**
* Retrieve all values in the source array
*
* @return array
*/
protected function getSourceValues() {
return array_keys($this->getSource());
}
/**
* Gets all valid values for this field.
*
* Does not include "empty" value if specified
*
* @return array
*/
public function getValidValues() {
$valid = array_diff($this->getSourceValues(), $this->getDisabledItems());
// Renumber indexes from 0
return array_values($valid);
}
/**
* Gets the source array not including any empty default values.
*
* @return array|ArrayAccess
*/
public function getSource() {
return $this->source;
}
/**
* Set the source for this list
*
* @param mixed $source
* @return $this
*/
public function setSource($source) {
$this->source = $this->getListMap($source);
return $this;
}
/**
* Given a list of values, extract the associative map of id => title
*
* @param mixed $source
* @return array Associative array of ids and titles
*/
protected function getListMap($source) {
// Extract source as an array
if($source instanceof SS_List) {
$source = $source->map();
}
if($source instanceof SS_Map) {
$source = $source->toArray();
}
if(!is_array($source) && !($source instanceof ArrayAccess)) {
user_error('$source passed in as invalid type', E_USER_ERROR);
}
return $source;
}
/**
* Given a non-array collection, extract the non-associative list of ids
* If passing as array, treat the array values (not the keys) as the ids
*
* @param mixed $values
* @return array Non-associative list of values
*/
protected function getListValues($values) {
// Empty values
if(empty($values)) {
return array();
}
// Direct array
if(is_array($values)) {
return array_values($values);
}
// Extract lists
if($values instanceof SS_List) {
return $values->column('ID');
}
return array(trim($values));
}
/**
* Determine if the current value of this field matches the given option value
*
* @param mixed $dataValue The value as extracted from the source of this field (or empty value if available)
* @param mixed $userValue The value as submitted by the user
* @return boolean True if the selected value matches the given option value
*/
public function isSelectedValue($dataValue, $userValue) {
if($dataValue === $userValue) {
return true;
}
// Allow null to match empty strings
if($dataValue === '' && $userValue === null) {
return true;
}
// Safety check against casting arrays as strings in PHP>5.4
if(is_array($dataValue) || is_array($userValue)) {
return false;
}
// For non-falsey values do loose comparison
if($dataValue) {
return $dataValue == $userValue;
}
// For empty values, use string comparison to perform visible value match
return ((string) $dataValue) === ((string) $userValue);
}
public function performReadonlyTransformation() {
/** @var LookupField $field */
$field = $this->castedCopy('LookupField');
$field->setSource($this->getSource());
$field->setReadonly(true);
return $field;
}
public function performDisabledTransformation() {
$clone = clone $this;
$clone->setDisabled(true);
return $clone;
}
/**
* Returns another instance of this field, but "cast" to a different class.
*
* @see FormField::castedCopy()
*
* @param String $classOrCopy
* @return FormField
*/
public function castedCopy($classOrCopy) {
$field = parent::castedCopy($classOrCopy);
if($field instanceof SelectField) {
$field->setSource($this->getSource());
}
return $field;
}
}