fieldDefinition->getFieldStorageDefinition()->getPropertyNames(); $this->column = $property_names[0]; } /** * {@inheritdoc} */ public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { // Prepare some properties for the child methods to build the actual form // element. $this->required = $element['#required']; $this->multiple = $this->fieldDefinition->getFieldStorageDefinition()->isMultiple(); $this->has_value = isset($items[0]->{$this->column}); // Add our custom validator. $element['#element_validate'][] = [get_class($this), 'validateElement']; $element['#key_column'] = $this->column; // The rest of the $element is built by child method implementations. return $element; } /** * Form validation handler for widget elements. * * @param array $element * The form element. * @param \Drupal\Core\Form\FormStateInterface $form_state * The form state. */ public static function validateElement(array $element, FormStateInterface $form_state) { if ($element['#required'] && $element['#value'] == '_none') { $form_state->setError($element, t('@name field is required.', ['@name' => $element['#title']])); } // Massage submitted form values. // Drupal\Core\Field\WidgetBase::submit() expects values as // an array of values keyed by delta first, then by column, while our // widgets return the opposite. if (is_array($element['#value'])) { $values = array_values($element['#value']); } else { $values = [$element['#value']]; } // Filter out the 'none' option. Use a strict comparison, because // 0 == 'any string'. $index = array_search('_none', $values, TRUE); if ($index !== FALSE) { unset($values[$index]); } // Transpose selections from field => delta to delta => field. $items = []; foreach ($values as $value) { $items[] = [$element['#key_column'] => $value]; } $form_state->setValueForElement($element, $items); } /** * Returns the array of options for the widget. * * @param \Drupal\Core\Entity\FieldableEntityInterface $entity * The entity for which to return options. * * @return array * The array of options for the widget. */ protected function getOptions(FieldableEntityInterface $entity) { if (!isset($this->options)) { // Limit the settable options for the current user account. $options = $this->fieldDefinition ->getFieldStorageDefinition() ->getOptionsProvider($this->column, $entity) ->getSettableOptions(\Drupal::currentUser()); // Add an empty option if the widget needs one. if ($empty_label = $this->getEmptyLabel()) { $options = ['_none' => $empty_label] + $options; } $module_handler = \Drupal::moduleHandler(); $context = [ 'fieldDefinition' => $this->fieldDefinition, 'entity' => $entity, ]; $module_handler->alter('options_list', $options, $context); array_walk_recursive($options, [$this, 'sanitizeLabel']); // Options might be nested ("optgroups"). If the widget does not support // nested options, flatten the list. if (!$this->supportsGroups()) { $options = OptGroup::flattenOptions($options); } $this->options = $options; } return $this->options; } /** * Determines selected options from the incoming field values. * * @param \Drupal\Core\Field\FieldItemListInterface $items * The field values. * * @return array * The array of corresponding selected options. */ protected function getSelectedOptions(FieldItemListInterface $items) { // We need to check against a flat list of options. $flat_options = OptGroup::flattenOptions($this->getOptions($items->getEntity())); $selected_options = []; foreach ($items as $item) { $value = $item->{$this->column}; // Keep the value if it actually is in the list of options (needs to be // checked against the flat list). if (isset($flat_options[$value])) { $selected_options[] = $value; } } return $selected_options; } /** * Indicates whether the widgets support optgroups. * * @return bool * TRUE if the widget supports optgroups, FALSE otherwise. */ protected function supportsGroups() { return FALSE; } /** * Sanitizes a string label to display as an option. * * @param string $label * The label to sanitize. */ protected function sanitizeLabel(&$label) { // Allow a limited set of HTML tags. $label = FieldFilteredMarkup::create($label); } /** * Returns the empty option label to add to the list of options, if any. * * @return string|null * Either a label of the empty option, or NULL. */ protected function getEmptyLabel() {} }