Issue #2015689 by smiletrl, jsacksick, mradcliffe, effulgentsia, nick_daffodil, stevector | swentel: Convert field type to typed data plugin for options module .

8.0.x
webchick 2014-01-13 10:06:09 -08:00
parent 4427812833
commit 074937a2d9
12 changed files with 568 additions and 498 deletions

View File

@ -0,0 +1,63 @@
<?php
/**
* @file
* Contains \Drupal\options\Type\ListBooleanItem.
*/
namespace Drupal\options\Plugin\Field\FieldType;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\TypedData\DataDefinition;
/**
* Plugin implementation of the 'list_boolean' field type.
*
* @FieldType(
* id = "list_boolean",
* module = "options",
* label = @Translation("Boolean"),
* description = @Translation("This field stores simple on/off or yes/no options."),
* default_widget = "options_buttons",
* default_formatter = "list_default",
* settings = {
* "allowed_values" = { },
* "allowed_values_function" = ""
* }
* )
*/
class ListBooleanItem extends ListItemBase {
/**
* Definitions of the contained properties.
*
* @var array
*/
static $propertyDefinitions;
/**
* {@inheritdoc}
*/
public static function schema(FieldDefinitionInterface $field_definition) {
return parent::schema($field_definition) + array(
'columns' => array(
'value' => array(
'type' => 'int',
'not null' => FALSE,
),
),
);
}
/**
* {@inheritdoc}
*/
public function getPropertyDefinitions() {
if (!isset(static::$propertyDefinitions)) {
static::$propertyDefinitions['value'] = DataDefinition::create('boolean')
->setLabel(t('Boolean value'));
}
return static::$propertyDefinitions;
}
}

View File

@ -0,0 +1,63 @@
<?php
/**
* @file
* Contains \Drupal\options\Type\ListFloatItem.
*/
namespace Drupal\options\Plugin\Field\FieldType;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\TypedData\DataDefinition;
/**
* Plugin implementation of the 'list_float' field type.
*
* @FieldType(
* id = "list_float",
* module = "options",
* label = @Translation("List (float)"),
* description = @Translation("This field stores float values from a list of allowed 'value => label' pairs, i.e. 'Fraction': 0 => 0, .25 => 1/4, .75 => 3/4, 1 => 1."),
* default_widget = "options_select",
* default_formatter = "list_default",
* settings = {
* "allowed_values" = { },
* "allowed_values_function" = ""
* }
* )
*/
class ListFloatItem extends ListItemBase {
/**
* Definitions of the contained properties.
*
* @var array
*/
static $propertyDefinitions;
/**
* {@inheritdoc}
*/
public static function schema(FieldDefinitionInterface $field_definition) {
return parent::schema($field_definition) + array(
'columns' => array(
'value' => array(
'type' => 'float',
'not null' => FALSE,
),
),
);
}
/**
* {@inheritdoc}
*/
public function getPropertyDefinitions() {
if (!isset(static::$propertyDefinitions)) {
static::$propertyDefinitions['value'] = DataDefinition::create('float')
->setLabel(t('Float value'));
}
return static::$propertyDefinitions;
}
}

View File

@ -0,0 +1,63 @@
<?php
/**
* @file
* Contains \Drupal\options\Type\ListIntegerItem.
*/
namespace Drupal\options\Plugin\Field\FieldType;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\TypedData\DataDefinition;
/**
* Plugin implementation of the 'list_integer' field type.
*
* @FieldType(
* id = "list_integer",
* module = "options",
* label = @Translation("List (integer)"),
* description = @Translation("This field stores integer values from a list of allowed 'value => label' pairs, i.e. 'Lifetime in days': 1 => 1 day, 7 => 1 week, 31 => 1 month."),
* default_widget = "options_select",
* default_formatter = "list_default",
* settings = {
* "allowed_values" = { },
* "allowed_values_function" = ""
* }
* )
*/
class ListIntegerItem extends ListItemBase {
/**
* Definitions of the contained properties.
*
* @var array
*/
static $propertyDefinitions;
/**
* {@inheritdoc}
*/
public static function schema(FieldDefinitionInterface $field_definition) {
return parent::schema($field_definition) + array(
'columns' => array(
'value' => array(
'type' => 'int',
'not null' => FALSE,
),
),
);
}
/**
* {@inheritdoc}
*/
public function getPropertyDefinitions() {
if (!isset(static::$propertyDefinitions)) {
static::$propertyDefinitions['value'] = DataDefinition::create('integer')
->setLabel(t('Integer value'));
}
return static::$propertyDefinitions;
}
}

View File

@ -0,0 +1,309 @@
<?php
/**
* @file
* Contains \Drupal\options\Type\ListItemBase.
*/
namespace Drupal\options\Plugin\Field\FieldType;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\ConfigFieldItemBase;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\TypedData\AllowedValuesInterface;
/**
* Plugin base class inherited by the options field types.
*/
abstract class ListItemBase extends ConfigFieldItemBase implements AllowedValuesInterface {
/**
* {@inheritdoc}
*/
public function getPossibleValues(AccountInterface $account = NULL) {
// Flatten options firstly, because Possible Options may contain group
// arrays.
$flatten_options = \Drupal::formBuilder()->flattenOptions($this->getPossibleOptions($account));
return array_keys($flatten_options);
}
/**
* {@inheritdoc}
*/
public function getPossibleOptions(AccountInterface $account = NULL) {
return $this->getSettableOptions($account);
}
/**
* {@inheritdoc}
*/
public function getSettableValues(AccountInterface $account = NULL) {
// Flatten options firstly, because Settable Options may contain group
// arrays.
$flatten_options = \Drupal::formBuilder()->flattenOptions($this->getSettableOptions($account));
return array_keys($flatten_options);
}
/**
* {@inheritdoc}
*/
public function getSettableOptions(AccountInterface $account = NULL) {
$allowed_options = options_allowed_values($this->getFieldDefinition(), $this->getEntity());
return $allowed_options;
}
/**
* {@inheritdoc}
*/
public static function schema(FieldDefinitionInterface $field_definition) {
return array(
'indexes' => array(
'value' => array('value'),
),
);
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, array &$form_state, $has_data) {
// @todo Move type-specific logic to the type-specific subclass:
// https://drupal.org/node/2169983.
$field_type = $this->getFieldDefinition()->getType();
$allowed_values = $this->getFieldSetting('allowed_values');
$allowed_values_function = $this->getFieldSetting('allowed_values_function');
if (in_array($field_type, array('list_integer', 'list_float', 'list_text'))) {
$element['allowed_values'] = array(
'#type' => 'textarea',
'#title' => t('Allowed values list'),
'#default_value' => $this->allowedValuesString($allowed_values),
'#rows' => 10,
'#element_validate' => array(array($this, 'validateAllowedValues')),
'#field_has_data' => $has_data,
'#access' => empty($allowed_values_function),
);
$description = '<p>' . t('The possible values this field can contain. Enter one value per line, in the format key|label.');
if ($field_type == 'list_integer' || $field_type == 'list_float') {
$description .= '<br/>' . t('The key is the stored value, and must be numeric. The label will be used in displayed values and edit forms.');
$description .= '<br/>' . t('The label is optional: if a line contains a single number, it will be used as key and label.');
$description .= '<br/>' . t('Lists of labels are also accepted (one label per line), only if the field does not hold any values yet. Numeric keys will be automatically generated from the positions in the list.');
}
else {
$description .= '<br/>' . t('The key is the stored value. The label will be used in displayed values and edit forms.');
$description .= '<br/>' . t('The label is optional: if a line contains a single string, it will be used as key and label.');
}
$description .= '</p>';
$element['allowed_values']['#description'] = $description;
}
elseif ($field_type == 'list_boolean') {
$values = $allowed_values;
$off_value = array_shift($values);
$on_value = array_shift($values);
$element['allowed_values'] = array(
'#type' => 'value',
'#description' => '',
'#value_callback' => 'options_field_settings_form_value_boolean_allowed_values',
'#access' => empty($allowed_values_function),
);
$element['allowed_values']['on'] = array(
'#type' => 'textfield',
'#title' => t('On value'),
'#default_value' => $on_value,
'#required' => FALSE,
'#description' => t('If left empty, "1" will be used.'),
// Change #parents to make sure the element is not saved into field
// settings.
'#parents' => array('on'),
);
$element['allowed_values']['off'] = array(
'#type' => 'textfield',
'#title' => t('Off value'),
'#default_value' => $off_value,
'#required' => FALSE,
'#description' => t('If left empty, "0" will be used.'),
// Change #parents to make sure the element is not saved into field
// settings.
'#parents' => array('off'),
);
// Link the allowed value to the on / off elements to prepare for the rare
// case of an alter changing #parents.
$element['allowed_values']['#on_parents'] = &$element['allowed_values']['on']['#parents'];
$element['allowed_values']['#off_parents'] = &$element['allowed_values']['off']['#parents'];
// Provide additional information about how to format allowed_values
// of a boolean field for use by a single on/off checkbox widget. Since
// the widget might not have been selected yet, can be changed independently
// of this form, and can vary by form mode, we display this information
// regardless of current widget selection.
$element['allowed_values']['#description'] .= '<p>' . t("For a 'single on/off checkbox' widget, define the 'off' value first, then the 'on' value in the <strong>Allowed values</strong> section. Note that the checkbox will be labeled with the label of the 'on' value.") . '</p>';
}
$element['allowed_values']['#description'] .= '<p>' . t('Allowed HTML tags in labels: @tags', array('@tags' => _field_filter_xss_display_allowed_tags())) . '</p>';
$element['allowed_values_function'] = array(
'#type' => 'item',
'#title' => t('Allowed values list'),
'#markup' => t('The value of this field is being determined by the %function function and may not be changed.', array('%function' => $allowed_values_function)),
'#access' => !empty($allowed_values_function),
'#value' => $allowed_values_function,
);
return $element;
}
/**
* Generates a string representation of an array of 'allowed values'.
*
* This string format is suitable for edition in a textarea.
*
* @param array $values
* An array of values, where array keys are values and array values are
* labels.
*
* @return string
* The string representation of the $values array:
* - Values are separated by a carriage return.
* - Each value is in the format "value|label" or "value".
*/
protected function allowedValuesString($values) {
$lines = array();
foreach ($values as $key => $value) {
$lines[] = "$key|$value";
}
return implode("\n", $lines);
}
/**
* Element validate callback; check that the entered values are valid.
*/
public function validateAllowedValues($element, &$form_state) {
// @todo Move type-specific logic to the type-specific subclass:
// https://drupal.org/node/2169983.
$field_type = $this->getFieldDefinition()->getType();
$has_data = $element['#field_has_data'];
$generate_keys = ($field_type == 'list_integer' || $field_type == 'list_float') && !$has_data;
$values = $this->extractAllowedValues($element['#value'], $generate_keys);
if (!is_array($values)) {
\Drupal::formBuilder()->setError($element, $form_state, t('Allowed values list: invalid input.'));
}
else {
// Check that keys are valid for the field type.
foreach ($values as $key => $value) {
if ($field_type == 'list_integer' && !preg_match('/^-?\d+$/', $key)) {
\Drupal::formBuilder()->setError($element, $form_state, t('Allowed values list: keys must be integers.'));
break;
}
if ($field_type == 'list_float' && !is_numeric($key)) {
\Drupal::formBuilder()->setError($element, $form_state, t('Allowed values list: each key must be a valid integer or decimal.'));
break;
}
elseif ($field_type == 'list_text' && drupal_strlen($key) > 255) {
\Drupal::formBuilder()->setError($element, $form_state, t('Allowed values list: each key must be a string at most 255 characters long.'));
break;
}
}
// Prevent removing values currently in use.
if ($has_data) {
$lost_keys = array_diff(array_keys($this->getFieldSetting('allowed_values')), array_keys($values));
if (_options_values_in_use($this->getEntity()->entityType(), $this->getFieldDefinition()->getName(), $lost_keys)) {
\Drupal::formBuilder()->setError($element, $form_state, t('Allowed values list: some values are being removed while currently in use.'));
}
}
form_set_value($element, $values, $form_state);
}
}
/**
* Parses a string of 'allowed values' into an array.
*
* @param string $string
* The list of allowed values in string format described in
* \Drupal\options\Plugin\Field\FieldType\ListItemBase::allowedValuesString().
* @param bool $generate_keys
* Boolean value indicating whether to generate keys based on the position
* of the value if a key is not manually specified, and if the value cannot
* be used as a key. This should only be TRUE for fields of type
* 'list_number'.
*
* @return array
* The array of extracted key/value pairs, or NULL if the string is invalid.
*
* @see \Drupal\options\Plugin\Field\FieldType\ListItemBase::allowedValuesString()
*/
protected function extractAllowedValues($string, $generate_keys) {
// @todo Move type-specific logic to the type-specific subclass:
// https://drupal.org/node/2169983.
$field_type = $this->getFieldDefinition()->getType();
$values = array();
$list = explode("\n", $string);
$list = array_map('trim', $list);
$list = array_filter($list, 'strlen');
$generated_keys = $explicit_keys = FALSE;
foreach ($list as $position => $text) {
$value = $key = FALSE;
// Check for an explicit key.
$matches = array();
if (preg_match('/(.*)\|(.*)/', $text, $matches)) {
// Trim key and value to avoid unwanted spaces issues.
$key = trim($matches[1]);
$value = trim($matches[2]);
$explicit_keys = TRUE;
}
// Otherwise see if we can use the value as the key. Detecting true integer
// strings takes a little trick.
elseif ($field_type == 'list_text'
|| ($field_type == 'list_float' && is_numeric($text))
|| ($field_type == 'list_integer' && is_numeric($text) && (float) $text == intval($text))) {
$key = $value = $text;
$explicit_keys = TRUE;
}
// Otherwise see if we can generate a key from the position.
elseif ($generate_keys) {
$key = (string) $position;
$value = $text;
$generated_keys = TRUE;
}
else {
return;
}
// Float keys are represented as strings and need to be disambiguated
// ('.5' is '0.5').
if ($field_type == 'list_float' && is_numeric($key)) {
$key = (string) (float) $key;
}
$values[$key] = $value;
}
// We generate keys only if the list contains no explicit key at all.
if ($explicit_keys && $generated_keys) {
return;
}
return $values;
}
/**
* {@inheritdoc}
*/
public function isEmpty() {
$value = $this->get('value')->getValue();
return empty($value) && (string) $value !== '0';
}
}

View File

@ -0,0 +1,65 @@
<?php
/**
* @file
* Contains \Drupal\options\Type\ListTextItem.
*/
namespace Drupal\options\Plugin\Field\FieldType;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\TypedData\DataDefinition;
/**
* Plugin implementation of the 'list_text' field type.
*
* @FieldType(
* id = "list_text",
* module = "options",
* label = @Translation("List (text)"),
* description = @Translation("This field stores text values from a list of allowed 'value => label' pairs, i.e. 'US States': IL => Illinois, IA => Iowa, IN => Indiana."),
* default_widget = "options_select",
* default_formatter = "list_default",
* settings = {
* "allowed_values" = { },
* "allowed_values_function" = ""
* }
* )
*/
class ListTextItem extends ListItemBase {
/**
* Definitions of the contained properties.
*
* @var array
*/
static $propertyDefinitions;
/**
* {@inheritdoc}
*/
public static function schema(FieldDefinitionInterface $field_definition) {
return parent::schema($field_definition) + array(
'columns' => array(
'value' => array(
'type' => 'varchar',
'length' => 255,
'not null' => FALSE,
),
),
);
}
/**
* {@inheritdoc}
*/
public function getPropertyDefinitions() {
if (!isset(static::$propertyDefinitions)) {
$constraints = array('Length' => array('max' => 255));
static::$propertyDefinitions['value'] = DataDefinition::create('string')
->setLabel(t('Text value'))
->setConstraints($constraints);
}
return static::$propertyDefinitions;
}
}

View File

@ -80,7 +80,7 @@ abstract class OptionsWidgetBase extends WidgetBase {
*/
public static function validateElement(array $element, array &$form_state) {
if ($element['#required'] && $element['#value'] == '_none') {
form_error($element, $form_state, t('!name field is required.', array('!name' => $element['#title'])));
\Drupal::formBuilder()->setError($element, $form_state, t('!name field is required.', array('!name' => $element['#title'])));
}
// Massage submitted form values.

View File

@ -1,13 +0,0 @@
<?php
/**
* @file
* Contains \Drupal\options\Type\ListBooleanItem.
*/
namespace Drupal\options\Type;
/**
* Defines the 'list_boolean' entity field item.
*/
class ListBooleanItem extends ListIntegerItem { }

View File

@ -1,38 +0,0 @@
<?php
/**
* @file
* Contains \Drupal\options\Type\ListFloatItem.
*/
namespace Drupal\options\Type;
use Drupal\Core\Field\Plugin\Field\FieldType\LegacyConfigFieldItem;
use Drupal\Core\TypedData\DataDefinition;
/**
* Defines the 'list_float' entity field item.
*/
class ListFloatItem extends LegacyConfigFieldItem {
/**
* Definitions of the contained properties.
*
* @see FloatItem::getPropertyDefinitions()
*
* @var array
*/
static $propertyDefinitions;
/**
* Implements ComplexDataInterface::getPropertyDefinitions().
*/
public function getPropertyDefinitions() {
if (!isset(static::$propertyDefinitions)) {
static::$propertyDefinitions['value'] = DataDefinition::create('float')
->setLabel(t('Float value'));
}
return static::$propertyDefinitions;
}
}

View File

@ -1,38 +0,0 @@
<?php
/**
* @file
* Contains \Drupal\options\Type\ListIntegerItem.
*/
namespace Drupal\options\Type;
use Drupal\Core\Field\Plugin\Field\FieldType\LegacyConfigFieldItem;
use Drupal\Core\TypedData\DataDefinition;
/**
* Defines the 'list_integer' entity field item.
*/
class ListIntegerItem extends LegacyConfigFieldItem {
/**
* Definitions of the contained properties.
*
* @see IntegerItem::getPropertyDefinitions()
*
* @var array
*/
static $propertyDefinitions;
/**
* Implements ComplexDataInterface::getPropertyDefinitions().
*/
public function getPropertyDefinitions() {
if (!isset(static::$propertyDefinitions)) {
static::$propertyDefinitions['value'] = DataDefinition::create('integer')
->setLabel(t('Integer value'));
}
return static::$propertyDefinitions;
}
}

View File

@ -1,39 +0,0 @@
<?php
/**
* @file
* Contains \Drupal\options\Type\ListTextItem.
*/
namespace Drupal\options\Type;
use Drupal\Core\Field\Plugin\Field\FieldType\LegacyConfigFieldItem;
use Drupal\Core\TypedData\DataDefinition;
/**
* Defines the 'list_text' configurable field type.
*/
class ListTextItem extends LegacyConfigFieldItem {
/**
* Definitions of the contained properties.
*
* @see TextItem::getPropertyDefinitions()
*
* @var array
*/
static $propertyDefinitions;
/**
* Implements ComplexDataInterface::getPropertyDefinitions().
*/
public function getPropertyDefinitions() {
if (!isset(static::$propertyDefinitions)) {
static::$propertyDefinitions['value'] = DataDefinition::create('string')
->setLabel(t('Text value'));
}
return static::$propertyDefinitions;
}
}

View File

@ -1,46 +0,0 @@
<?php
/**
* @file
* Install, update and uninstall functions for the options module.
*/
/**
* Implements hook_field_schema().
*/
function options_field_schema($field) {
switch ($field->getType()) {
case 'list_text':
$columns = array(
'value' => array(
'type' => 'varchar',
'length' => 255,
'not null' => FALSE,
),
);
break;
case 'list_float':
$columns = array(
'value' => array(
'type' => 'float',
'not null' => FALSE,
),
);
break;
case 'list_integer':
case 'list_boolean':
$columns = array(
'value' => array(
'type' => 'int',
'not null' => FALSE,
),
);
break;
}
return array(
'columns' => $columns,
'indexes' => array(
'value' => array('value'),
),
);
}

View File

@ -24,184 +24,6 @@ function options_help($path, $arg) {
}
}
/**
* Implements hook_field_info().
*/
function options_field_info() {
return array(
'list_integer' => array(
'label' => t('List (integer)'),
'description' => t("This field stores integer values from a list of allowed 'value => label' pairs, i.e. 'Lifetime in days': 1 => 1 day, 7 => 1 week, 31 => 1 month."),
'settings' => array('allowed_values' => array(), 'allowed_values_function' => ''),
'default_widget' => 'options_select',
'default_formatter' => 'list_default',
'class' => '\Drupal\options\Type\ListIntegerItem',
),
'list_float' => array(
'label' => t('List (float)'),
'description' => t("This field stores float values from a list of allowed 'value => label' pairs, i.e. 'Fraction': 0 => 0, .25 => 1/4, .75 => 3/4, 1 => 1."),
'settings' => array('allowed_values' => array(), 'allowed_values_function' => ''),
'default_widget' => 'options_select',
'default_formatter' => 'list_default',
'class' => '\Drupal\options\Type\ListFloatItem',
),
'list_text' => array(
'label' => t('List (text)'),
'description' => t("This field stores text values from a list of allowed 'value => label' pairs, i.e. 'US States': IL => Illinois, IA => Iowa, IN => Indiana."),
'settings' => array('allowed_values' => array(), 'allowed_values_function' => ''),
'default_widget' => 'options_select',
'default_formatter' => 'list_default',
'class' => '\Drupal\options\Type\ListTextItem',
),
'list_boolean' => array(
'label' => t('Boolean'),
'description' => t('This field stores simple on/off or yes/no options.'),
'settings' => array('allowed_values' => array(), 'allowed_values_function' => ''),
'default_widget' => 'options_buttons',
'default_formatter' => 'list_default',
'class' => '\Drupal\options\Type\ListBooleanItem',
),
);
}
/**
* Implements hook_field_settings_form().
*/
function options_field_settings_form($field, $instance) {
$settings = $field->getSettings();
$field_type = $field->getType();
switch ($field_type) {
case 'list_integer':
case 'list_float':
case 'list_text':
$form['allowed_values'] = array(
'#type' => 'textarea',
'#title' => t('Allowed values list'),
'#default_value' => options_allowed_values_string($settings['allowed_values']),
'#rows' => 10,
'#element_validate' => array('options_field_settings_form_validate_allowed_values'),
'#field_has_data' => $field->hasData(),
'#field' => $field,
'#field_type' => $field_type,
'#access' => empty($settings['allowed_values_function']),
);
$description = '<p>' . t('The possible values this field can contain. Enter one value per line, in the format key|label.');
if ($field_type == 'list_integer' || $field_type == 'list_float') {
$description .= '<br/>' . t('The key is the stored value, and must be numeric. The label will be used in displayed values and edit forms.');
$description .= '<br/>' . t('The label is optional: if a line contains a single number, it will be used as key and label.');
$description .= '<br/>' . t('Lists of labels are also accepted (one label per line), only if the field does not hold any values yet. Numeric keys will be automatically generated from the positions in the list.');
}
else {
$description .= '<br/>' . t('The key is the stored value. The label will be used in displayed values and edit forms.');
$description .= '<br/>' . t('The label is optional: if a line contains a single string, it will be used as key and label.');
}
$description .= '</p>';
$form['allowed_values']['#description'] = $description;
break;
case 'list_boolean':
$values = $settings['allowed_values'];
$off_value = array_shift($values);
$on_value = array_shift($values);
$form['allowed_values'] = array(
'#type' => 'value',
'#description' => '',
'#value_callback' => 'options_field_settings_form_value_boolean_allowed_values',
'#access' => empty($settings['allowed_values_function']),
);
$form['allowed_values']['on'] = array(
'#type' => 'textfield',
'#title' => t('On value'),
'#default_value' => $on_value,
'#required' => FALSE,
'#description' => t('If left empty, "1" will be used.'),
// Change #parents to make sure the element is not saved into field
// settings.
'#parents' => array('on'),
);
$form['allowed_values']['off'] = array(
'#type' => 'textfield',
'#title' => t('Off value'),
'#default_value' => $off_value,
'#required' => FALSE,
'#description' => t('If left empty, "0" will be used.'),
// Change #parents to make sure the element is not saved into field
// settings.
'#parents' => array('off'),
);
// Link the allowed value to the on / off elements to prepare for the rare
// case of an alter changing #parents.
$form['allowed_values']['#on_parents'] = &$form['allowed_values']['on']['#parents'];
$form['allowed_values']['#off_parents'] = &$form['allowed_values']['off']['#parents'];
break;
}
// Alter the description for allowed values depending on the widget type.
if ($field_type == 'list_boolean') {
$form['allowed_values']['#description'] .= '<p>' . t("For a 'single on/off checkbox' widget, define the 'off' value first, then the 'on' value in the <strong>Allowed values</strong> section. Note that the checkbox will be labeled with the label of the 'on' value.") . '</p>';
}
$form['allowed_values']['#description'] .= '<p>' . t('Allowed HTML tags in labels: @tags', array('@tags' => _field_filter_xss_display_allowed_tags())) . '</p>';
$form['allowed_values_function'] = array(
'#type' => 'item',
'#title' => t('Allowed values list'),
'#markup' => t('The value of this field is being determined by the %function function and may not be changed.', array('%function' => $settings['allowed_values_function'])),
'#access' => !empty($settings['allowed_values_function']),
'#value' => $settings['allowed_values_function'],
);
return $form;
}
/**
* Element validate callback; check that the entered values are valid.
*/
function options_field_settings_form_validate_allowed_values($element, &$form_state) {
$field = $element['#field'];
$has_data = $element['#field_has_data'];
$field_type = $field->getType();
$generate_keys = ($field_type == 'list_integer' || $field_type == 'list_float') && !$has_data;
$values = options_extract_allowed_values($element['#value'], $field_type, $generate_keys);
if (!is_array($values)) {
form_error($element, $form_state, t('Allowed values list: invalid input.'));
}
else {
// Check that keys are valid for the field type.
foreach ($values as $key => $value) {
if ($field_type == 'list_integer' && !preg_match('/^-?\d+$/', $key)) {
form_error($element, $form_state, t('Allowed values list: keys must be integers.'));
break;
}
if ($field_type == 'list_float' && !is_numeric($key)) {
form_error($element, $form_state, t('Allowed values list: each key must be a valid integer or decimal.'));
break;
}
elseif ($field_type == 'list_text' && drupal_strlen($key) > 255) {
form_error($element, $form_state, t('Allowed values list: each key must be a string at most 255 characters long.'));
break;
}
}
// Prevent removing values currently in use.
if ($has_data) {
$lost_keys = array_diff(array_keys($field->getSetting('allowed_values')), array_keys($values));
if (_options_values_in_use($field, $lost_keys)) {
form_error($element, $form_state, t('Allowed values list: some values are being removed while currently in use.'));
}
}
form_set_value($element, $values, $form_state);
}
}
/**
* Form element #value_callback: assembles the allowed values for 'boolean' fields.
*/
@ -268,100 +90,6 @@ function options_allowed_values(FieldDefinitionInterface $field_definition, Enti
return $allowed_values[$cache_id];
}
/**
* Parses a string of 'allowed values' into an array.
*
* @param $string
* The list of allowed values in string format described in
* options_allowed_values_string().
* @param $field_type
* The field type. Either 'list_number' or 'list_text'.
* @param $generate_keys
* Boolean value indicating whether to generate keys based on the position of
* the value if a key is not manually specified, and if the value cannot be
* used as a key. This should only be TRUE for fields of type 'list_number'.
*
* @return
* The array of extracted key/value pairs, or NULL if the string is invalid.
*
* @see options_allowed_values_string()
*/
function options_extract_allowed_values($string, $field_type, $generate_keys) {
$values = array();
$list = explode("\n", $string);
$list = array_map('trim', $list);
$list = array_filter($list, 'strlen');
$generated_keys = $explicit_keys = FALSE;
foreach ($list as $position => $text) {
$key = FALSE;
// Check for an explicit key.
$matches = array();
if (preg_match('/(.*)\|(.*)/', $text, $matches)) {
// Trim key and value to avoid unwanted spaces issues.
$key = trim($matches[1]);
$value = trim($matches[2]);
$explicit_keys = TRUE;
}
// Otherwise see if we can use the value as the key. Detecting true integer
// strings takes a little trick.
elseif ($field_type == 'list_text'
|| ($field_type == 'list_float' && is_numeric($text))
|| ($field_type == 'list_integer' && is_numeric($text) && (float) $text == intval($text))) {
$key = $value = $text;
$explicit_keys = TRUE;
}
// Otherwise see if we can generate a key from the position.
elseif ($generate_keys) {
$key = (string) $position;
$value = $text;
$generated_keys = TRUE;
}
else {
return;
}
// Float keys are represented as strings and need to be disambiguated
// ('.5' is '0.5').
if ($field_type == 'list_float' && is_numeric($key)) {
$key = (string) (float) $key;
}
$values[$key] = $value;
}
// We generate keys only if the list contains no explicit key at all.
if ($explicit_keys && $generated_keys) {
return;
}
return $values;
}
/**
* Generates a string representation of an array of 'allowed values'.
*
* This string format is suitable for edition in a textarea.
*
* @param $values
* An array of values, where array keys are values and array values are
* labels.
*
* @return
* The string representation of the $values array:
* - Values are separated by a carriage return.
* - Each value is in the format "value|label" or "value".
*/
function options_allowed_values_string($values) {
$lines = array();
foreach ($values as $key => $value) {
$lines[] = "$key|$value";
}
return implode("\n", $lines);
}
/**
* Implements hook_field_update_forbid().
*/
@ -371,7 +99,7 @@ function options_field_update_forbid($field, $prior_field) {
$allowed_values = $field->getSetting('allowed_values');
$prior_allowed_values = $prior_field->getSetting('allowed_values');
$lost_keys = array_diff(array_keys($prior_allowed_values), array_keys($allowed_values));
if (_options_values_in_use($field, $lost_keys)) {
if (_options_values_in_use($field->entity_type, $field->getName(), $lost_keys)) {
throw new FieldUpdateForbiddenException(t('A list field (@field_name) with existing data cannot have its keys changed.', array('@field_name' => $field->getName())));
}
}
@ -380,11 +108,11 @@ function options_field_update_forbid($field, $prior_field) {
/**
* Checks if a list of values are being used in actual field values.
*/
function _options_values_in_use(FieldInterface $field, $values) {
function _options_values_in_use($entity_type, $field_name, $values) {
if ($values) {
$factory = \Drupal::service('entity.query');
$result = $factory->get($field->entity_type)
->condition($field->getName() . '.value', $values)
$result = $factory->get($entity_type)
->condition($field_name . '.value', $values)
->count()
->accessCheck(FALSE)
->range(0, 1)
@ -396,50 +124,3 @@ function _options_values_in_use(FieldInterface $field, $values) {
return FALSE;
}
/**
* Implements hook_field_validate().
*
* Possible error codes:
* - 'list_illegal_value': The value is not part of the list of allowed values.
*/
function options_field_validate(EntityInterface $entity = NULL, $field, $instance, $langcode, $items, &$errors) {
// When configuring a field instance, the default value is validated without
// an entity, but options_allowed_values() and the callback it invokes
// require an entity, because the result can depend on entity type, bundle,
// and other entity data.
if (!isset($entity)) {
$ids = (object) array('entity_type' => $instance->entity_type, 'bundle' => $instance->bundle, 'entity_id' => NULL);
$entity = _field_create_entity_from_ids($ids);
}
$allowed_values = options_allowed_values($instance, $entity);
foreach ($items as $delta => $item) {
if (!empty($item['value'])) {
if (!empty($allowed_values) && !isset($allowed_values[$item['value']])) {
$errors[$instance->getName()][$langcode][$delta][] = array(
'error' => 'list_illegal_value',
'message' => t('%name: illegal value.', array('%name' => $instance->getLabel())),
);
}
}
}
}
/**
* Implements hook_field_is_empty().
*/
function options_field_is_empty($item, $field_type) {
if (empty($item['value']) && (string) $item['value'] !== '0') {
return TRUE;
}
return FALSE;
}
/**
* Implements hook_options_list().
*/
function options_options_list(FieldDefinitionInterface $field_definition, EntityInterface $entity) {
return options_allowed_values($field_definition, $entity);
}