htools-8.x-1.x-dev/modules/options_element/src/Element/Options.php

modules/options_element/src/Element/Options.php
<?php
/**
 * @file
 * Contains \Drupal\options_element\Element\Options.
 */

namespace Drupal\options_element\Element;

use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Render\Element\FormElement;
use Drupal\options_element\OptionsUtilities;

/**
 * Defines the 'options' form element type.
 *
 * The 'options' form element type is useful when collecting a series of
 * values in a list. The values within the list may optionally have unique
 * keys, such as that in an array structure. In addition, a default choice
 * (or several default choices) may be selected by the user.
 *
 * @code
 * $element['options'] = array(
 *   '#type' => 'options',
 *   '#limit' => 20,
 *   '#optgroups' => FALSE,
 *   '#multiple' => FALSE,
 *   '#options' => array(
 *     'foo' => 'foo',
 *     'bar' => 'bar',
 *     'baz' => 'baz',
 *   ),
 *   '#default_value' => 'foo'
 *   '#key_type' => 'associative',
 * );
 * @endcode
 *
 * Properties for the 'options' element include:
 * - limit: The maximum number of options that can be added to a list. Defaults
 *   to 100.
 * - optgroups: If nesting of options is supported, up to one level. This is
 *   used when building a select HTML element that uses optgroups. Defaults to
 *   FALSE.
 * - multiple: Affects the number of default values that may be selected.
 * - default_value: The key(s) for the options that are currently selected. If
 *   #multiple is TRUE then, the default value is an array, otherwise it is a
 *   string.
 * - options: An array of options currently within the list.
 * - key_type: The method by which keys are determined for each value in the
 *   option list. Available options include:
 *   - mixed: Each value is not given any ID automatically, but any manually
 *     specified keys will be retained. This most emulates the existing
 *     conventions within Drupal, where keys are optional but allowed.
 *   - numeric: Each value is automatically given a unique numeric ID. This can
 *     be useful when wanting duplicate values in a list and not have to bother
 *     the end-user for keys.
 *   - associative: Keys are automatically mapped from the user-entered values.
 *     This is equivalent to making key|value pairs, but both the key and value
 *     are the same. Each key must be unique.
 *   - custom: Keys are manually entered by the end user. A second set of
 *     textfields are presented to let the user provide keys as well as values.
 *   - none: No keys are specified at all. This effectively creates numeric keys
 *     but unlike numeric keys, the keys are renumbered if the options in the
 *     list are rearranged.
 * - key_type_toggle: If specified, a checkbox will be added that allows the
 *   user to toggle between the current key type and the "custom" key type,
 *   letting them customize the keys as desired. This option has no effect with
 *   the "none" key type.
 * - key_type_toggled: Determine if the toggle checkbox is set or not by
 *   default.
 * - default_value_allowed: Indicates whether the end user should be able to
 *   modify the default value when editing the options list. Defaults to TRUE.
 * - default_value_pattern: If allowing dynamic default value keys, such as a
 *   token, specify a regular expression pattern that will also be allowed as
 *   a default value. Include pattern delimiters. Defaults to an empty string.
 *
 *   @code
 *   $element['options'] = array(
 *     '#type' => 'options',
 *     '#key_type' => 'associative',
 *     '#key_type_toggle' => t('Custom keys'),
 *     '#key_type_toggled' => TRUE,
 *   );
 *   @endcode
 *
 * @FormElement("options")
 */
class Options extends FormElement {
  public function getInfo() {
    $class = get_class($this);
    return array(
      '#input' => TRUE,
      '#limit' => NULL,
      '#optgroups' => TRUE,
      '#multiple' => FALSE,
      '#options' => array(),
      '#options_readonly' => FALSE,
      '#key_type' => 'mixed',
      '#key_type_toggle' => NULL,
      '#key_type_toggled' => FALSE,
      '#default_value_allowed' => TRUE,
      '#default_value_pattern' => '',
      '#process' => array(
        array($class, 'processOptions'),
      ),
      '#pre_render' => array(
        array($class, 'preRenderOptions'),
      ),
      '#element_validate' => array(
        array($class, 'validateOptions'),
      ),
      '#theme_wrappers' => array('form_element'),
    );

  }

  /**
   * Expand the "options" form element type.
   *
   * The "options" type is simply an enhanced textarea that makes it easier to
   * create key|value pairs and put items into optgroups.

   * @param array $element
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   * @param array $complete_form
   * @return array
   */
  public static function processOptions(&$element, FormStateInterface $form_state, &$complete_form) {
    $element['#options'] = isset($element['#options']) ? $element['#options'] : array();
    $element['#multiple'] = isset($element['#multiple']) ? $element['#multiple'] : FALSE;

    $element['#tree'] = TRUE;
    $element['#theme'] = 'options';

    $element['#attached']['library'][] =  'options_element/options_element';

    // Add the key type toggle checkbox.
    if (!isset($element['custom_keys']) && $element['#key_type'] != 'custom' && !empty($element['#key_type_toggle'])) {
      $element['custom_keys'] = array(
        '#title' => is_string($element['#key_type_toggle']) ? $element['#key_type_toggle'] : t('Customize keys'),
        '#type' => 'checkbox',
        '#default_value' => $element['#key_type_toggled'],
        '#attributes' => array('class' => array('key-type-toggle')),
        '#description' => t('Customizing the keys will allow you to save one value internally while showing a different option to the user.'),
      );
    }

    // Add the multiple value toggle checkbox.
    if (!isset($element['multiple']) && !empty($element['#multiple_toggle'])) {
      $element['multiple'] = array(
        '#title' => is_string($element['#multiple_toggle']) ? $element['#multiple_toggle'] : t('Allow multiple values'),
        '#type' => 'checkbox',
        '#default_value' => !empty($element['#multiple']),
        '#attributes' => array('class' => array('multiple-toggle')),
        '#description' => t('Multiple values will let users select multiple items in this list.'),
      );
    }
    // If the element had a custom interface for toggling whether or not multiple
    // values are accepted, make sure that form_type_options_value() knows to use
    // it.
    if (isset($element['multiple']) && empty($element['#multiple_toggle'])) {
      $element['#multiple_toggle'] = TRUE;
    }

    // Add the main textarea for adding options.
    if (!isset($element['options'])) {
      $element['options_field'] = array(
        '#type' => 'textarea',
        '#resizable' => TRUE,
        '#cols' => 60,
        '#rows' => 5,
        '#required' => isset($element['#required']) ? $element['#required'] : FALSE,
        '#description' => t('List options one option per line.'),
        '#attributes' => $element['#options_readonly'] ? array('readonly' => 'readonly') : array(),
        '#wysiwyg' => FALSE, // Prevent CKeditor from trying to hijack.
      );

      // If validation fails, reload the user's text even if it's not valid.
      if (isset($element['#value']['options_text'])) {
        $element['options_field']['#value'] = $element['#value']['options_text'];
      }
      // Most of the time, we'll be converting the options array into the text.
      else {
        $element['options_field']['#value'] = isset($element['#options']) ? OptionsUtilities::optionsToText($element['#options'], $element['#key_type']) : '';
      }


      if ($element['#key_type'] == 'mixed' || $element['#key_type'] == 'numeric' || $element['#key_type'] == 'custom') {
        $element['options_field']['#description'] .= ' ' . t('Key-value pairs may be specified by separating each option with pipes, such as <em>key|value</em>.');
      }
      elseif ($element['#key_type_toggle']) {
        $element['options_field']['#description'] .= ' ' . t('If the %toggle field is checked, key-value pairs may be specified by separating each option with pipes, such as <em>key|value</em>.', array('%toggle' => $element['custom_keys']['#title']));
      }
      if ($element['#key_type'] == 'numeric') {
        $element['options_field']['#description'] .= ' ' . t('This field requires all specified keys to be integers.');
      }
    }

    // Add the field for storing default values.
    if ($element['#default_value_allowed'] && !isset($element['default_value_field'])) {
      $element['default_value_field'] = array(
        '#title' => t('Default value'),
        '#type' => 'textfield',
        '#size' => 60,
        '#maxlength' => 1024,
        '#value' => isset($element['#default_value']) ? ($element['#multiple'] ? implode(', ', (array) $element['#default_value']) : $element['#default_value']) : '',
        '#description' => t('Specify the keys that should be selected by default.'),
      );
      if ($element['#multiple']) {
        $element['default_value_field']['#description'] .= ' ' . t('Multiple default values may be specified by separating keys with commas.');
      }
    }

    // Add the field for storing a default value pattern.
    if ($element['#default_value_pattern']) {
      $element['default_value_pattern'] = array(
        '#type' => 'hidden',
        '#value' => $element['#default_value_pattern'],
        '#attributes' => array('class' => array('default-value-pattern')),
      );
    }

    // Remove properties that will confuse the FAPI.
    unset($element['#options']);

    return $element;
  }

  /**
   * Validate the "options" form element type.
   *
   * @param array $element
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   * @param array $complete_form
   */
  public static function validateOptions(&$element, FormStateInterface $form_state, &$complete_form) {

    // Even though we already have the converted options in #value['options'], run
    // the conversion again to check for duplicates in the user-defined list.
    $duplicates = array();
    $options = OptionsUtilities::optionsFromText($element['#value']['options_text'], $element['#key_type'], empty($element['#optgroups']), $duplicates);

    // Check if a key is used multiple times.
    if (count($duplicates) == 1) {
      $form_state->setError($element, t('The key %key has been used multiple times. Each key must be unique to display properly.', array('%key' => reset($duplicates))));
    }
    elseif (!empty($duplicates)) {
      array_walk($duplicates, 'check_plain');
      $duplicate_list = [
        '#theme' => 'item_list',
        '#items' => $duplicates
      ];

      $form_state->setError($element, t('The following keys have been used multiple times. Each key must be unique to display properly.') . $duplicate_list);
    }

    // Add the list of duplicates to the page so that we can highlight the fields.
    if (!empty($duplicates)) {
      $element['#attached']['drupalSettings']['optionsElement']['errors'] = array_combine($duplicates, $duplicates);
    }

    // Check if no options are specified.
    if (empty($options) && $element['#required']) {
      $form_state->setError($element, t('At least one option must be specified.'));
    }

    // Check for numeric keys if needed.
    if ($element['#key_type'] == 'numeric') {
      foreach ($options as $key => $value) {
        if (!is_int($key)) {
          $form_state->setError($element, t('The keys for the %title field must be integers.', array('%title' => $element['#title'])));
          break;
        }
      }
    }

    // Check that the limit of options has not been exceeded.
    if (!empty($element['#limit'])) {
      $count = 0;
      foreach ($options as $value) {
        if (is_array($value)) {
          $count += count($value);
        }
        else {
          $count++;
        }
      }
      if ($count > $element['#limit']) {
        $form_state->setError($element, t('The %title field supports a maximum of @count options. Please reduce the number of options.', array('%title' => $element['#title'], '@count' => $element['#limit'])));
      }
    }
  }

  /**
   * This function adjusts the value of the element from a text value to an array.
   *
   * @param array $element
   * @param array|FALSE $input
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   * @return array
   */
  public static function valueCallback(&$element, $input, FormStateInterface $form_state) {

    if ($input === FALSE) {
      return array(
        'options' => isset($element['#options']) ? $element['#options'] : array(),
        'default_value' => isset($element['#default_value']) ? $element['#default_value'] : '',
      );
    }
    else {
      // Convert text to an array of options.
      $duplicates = array();
      $options = OptionsUtilities::optionsFromText($input['options_field'], $element['#key_type'], empty($element['#optgroups']), $duplicates);

      // Convert default value.
      if (isset($input['default_value_field'])) {
        // If the element supports toggling whether or not it will accept
        // multiple values, use the value that was passed in via $input (keeping
        // in mind that this value may not be set, if a checkbox was used to
        // configure it). Otherwise, use the current setting stored with the
        // element itself.
        $multiple = !empty($element['#multiple_toggle']) ? !empty($input['multiple']) : !empty($element['#multiple']);
        if ($multiple) {
          $default_value = array();
          $default_items = explode(',', $input['default_value_field']);
          foreach ($default_items as $key) {
            $key = trim($key);
            $value = OptionsUtilities::optionsSearch($key, $options, $element['#default_value_pattern']);
            if (!is_null($value)) {
              $default_value[] = $value;
            }
          }
        }
        else {
          $default_value = OptionsUtilities::optionsSearch(trim($input['default_value_field']), $options, $element['#default_value_pattern']);
        }
      }
      else {
        $default_value = NULL;
      }

      $return = array(
        'options' => $options,
        'default_value' => $default_value,
        'options_text' => $input['options_field'],
      );

      if (isset($input['default_value_field'])) {
        $return['default_value_text'] = $input['default_value_field'];
      }

      return $return;
    }
  }


  /**
   * Prepares a #type 'options' render element for input.html.twig.
   *
   * @param array $element
   *   An associative array containing the properties of the element.
   *   Properties used: #title, #value, #return_value, #description, #required,
   *   #attributes, #checked.
   *
   * @return array
   *   The $element with prepared variables ready for options.html.twig.
   */
  public static function preRenderOptions($element) {
    $element['#attributes']['type'] = 'options';

    Element::setAttributes($element, array('id', 'name', '#return_value' => 'value'));

    $element['#attributes']['class'][] = 'form-options';

    $classes = &$element['#attributes']['class'];
    $classes[] = 'options-key-type-'. $element['#key_type'];

    if ($element['#key_type_toggled']) {
      $classes[] = 'options-key-custom';
    }

    if (isset($element['#optgroups']) && $element['#optgroups']) {
      $classes[] = 'options-optgroups';
    }

    if (isset($element['#multiple']) && $element['#multiple']) {
      $classes[] = 'options-multiple';
    }

    // Replace the error class from wrapper div, which doesn't display well with
    // complex elements like Options Element.
    if ($key = array_search('error', $classes, TRUE)) {
      $classes[$key] = 'options-error';
    }

    return $element;

  }
}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc