MachineName.php 8.84 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
<?php

/**
 * @file
 * Contains \Drupal\Core\Render\Element\MachineName.
 */

namespace Drupal\Core\Render\Element;

use Drupal\Component\Utility\NestedArray;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Form\FormStateInterface;

/**
 * Provides a machine name render element.
 *
 * Provides a form element to enter a machine name, which is validated to ensure
 * that the name is unique and does not contain disallowed characters. All
 * disallowed characters are replaced with a replacement character via
 * JavaScript.
 *
 * @FormElement("machine_name")
 */
class MachineName extends Textfield {

  /**
   * {@inheritdoc}
   */
  public function getInfo() {
    $class = get_class($this);
    return array(
      '#input' => TRUE,
      '#default_value' => NULL,
      '#required' => TRUE,
      '#maxlength' => 64,
      '#size' => 60,
      '#autocomplete_route_name' => FALSE,
      '#process' => array(
        array($class, 'processMachineName'),
        array($class, 'processAutocomplete'),
        array($class, 'processAjaxForm'),
      ),
      '#element_validate' => array(
        array($class, 'validateMachineName'),
      ),
      '#pre_render' => array(
        array($class, 'preRenderTextfield'),
      ),
      '#theme' => 'input__textfield',
      '#theme_wrappers' => array('form_element'),
    );
  }

  /**
   * {@inheritdoc}
   */
  public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
    return NULL;
  }

  /**
   * Processes a machine-readable name form element.
   *
   * @param array $element
   *   The form element to process. Properties used:
   *   - #machine_name: An associative array containing:
   *     - exists: A callable to invoke for checking whether a submitted machine
   *       name value already exists. The submitted value is passed as an
   *       argument. In most cases, an existing API or menu argument loader
   *       function can be re-used. The callback is only invoked if the
   *       submitted value differs from the element's #default_value.
   *     - source: (optional) The #array_parents of the form element containing
   *       the human-readable name (i.e., as contained in the $form structure)
   *       to use as source for the machine name. Defaults to array('label').
   *     - label: (optional) Text to display as label for the machine name value
   *       after the human-readable name form element. Defaults to "Machine
   *       name".
   *     - replace_pattern: (optional) A regular expression (without delimiters)
   *       matching disallowed characters in the machine name. Defaults to
   *       '[^a-z0-9_]+'.
   *     - replace: (optional) A character to replace disallowed characters in
   *       the machine name via JavaScript. Defaults to '_' (underscore). When
   *       using a different character, 'replace_pattern' needs to be set
   *       accordingly.
   *     - error: (optional) A custom form error message string to show, if the
   *       machine name contains disallowed characters.
   *     - standalone: (optional) Whether the live preview should stay in its
   *       own form element rather than in the suffix of the source
   *       element. Defaults to FALSE.
   *   - #maxlength: (optional) Maximum allowed length of the machine name.
   *     Defaults to 64.
   *   - #disabled: (optional) Should be set to TRUE if an existing machine
   *     name must not be changed after initial creation.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   * @param array $complete_form
   *   The complete form structure.
   *
   * @return array
   *   The processed element.
   */
  public static function processMachineName(&$element, FormStateInterface $form_state, &$complete_form) {
    // We need to pass the langcode to the client.
    $language = \Drupal::languageManager()->getCurrentLanguage();

    // Apply default form element properties.
    $element += array(
      '#title' => t('Machine-readable name'),
      '#description' => t('A unique machine-readable name. Can only contain lowercase letters, numbers, and underscores.'),
      '#machine_name' => array(),
      '#field_prefix' => '',
      '#field_suffix' => '',
      '#suffix' => '',
    );
    // A form element that only wants to set one #machine_name property (usually
    // 'source' only) would leave all other properties undefined, if the defaults
    // were defined in hook_element_info(). Therefore, we apply the defaults here.
    $element['#machine_name'] += array(
      'source' => array('label'),
      'target' => '#' . $element['#id'],
      'label' => t('Machine name'),
      'replace_pattern' => '[^a-z0-9_]+',
      'replace' => '_',
      'standalone' => FALSE,
      'field_prefix' => $element['#field_prefix'],
      'field_suffix' => $element['#field_suffix'],
    );

    // By default, machine names are restricted to Latin alphanumeric characters.
    // So, default to LTR directionality.
    if (!isset($element['#attributes'])) {
      $element['#attributes'] = array();
    }
    $element['#attributes'] += array('dir' => 'ltr');

136
    // The source element defaults to array('name'), but may have been overridden.
137 138 139 140 141 142 143 144
    if (empty($element['#machine_name']['source'])) {
      return $element;
    }

    // Retrieve the form element containing the human-readable name from the
    // complete form in $form_state. By reference, because we may need to append
    // a #field_suffix that will hold the live preview.
    $key_exists = NULL;
145
    $source = NestedArray::getValue($form_state->getCompleteForm(), $element['#machine_name']['source'], $key_exists);
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
    if (!$key_exists) {
      return $element;
    }

    $suffix_id = $source['#id'] . '-machine-name-suffix';
    $element['#machine_name']['suffix'] = '#' . $suffix_id;

    if ($element['#machine_name']['standalone']) {
      $element['#suffix'] = SafeMarkup::set($element['#suffix'] . ' <small id="' . $suffix_id . '">&nbsp;</small>');
    }
    else {
      // Append a field suffix to the source form element, which will contain
      // the live preview of the machine name.
      $source += array('#field_suffix' => '');
      $source['#field_suffix'] = SafeMarkup::set($source['#field_suffix'] . ' <small id="' . $suffix_id . '">&nbsp;</small>');

      $parents = array_merge($element['#machine_name']['source'], array('#field_suffix'));
163
      NestedArray::setValue($form_state->getCompleteForm(), $parents, $source['#field_suffix']);
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
    }

    $js_settings = array(
      'type' => 'setting',
      'data' => array(
        'machineName' => array(
          '#' . $source['#id'] => $element['#machine_name'],
        ),
        'langcode' => $language->id,
      ),
    );
    $element['#attached']['library'][] = 'core/drupal.machine-name';
    $element['#attached']['js'][] = $js_settings;

    return $element;
  }

  /**
   * Form element validation handler for machine_name elements.
   *
   * Note that #maxlength is validated by _form_validate() already.
   *
   * This checks that the submitted value:
   * - Does not contain the replacement character only.
   * - Does not contain disallowed characters.
   * - Is unique; i.e., does not already exist.
   * - Does not exceed the maximum length (via #maxlength).
   * - Cannot be changed after creation (via #disabled).
   */
  public static function validateMachineName(&$element, FormStateInterface $form_state, &$complete_form) {
    // Verify that the machine name not only consists of replacement tokens.
    if (preg_match('@^' . $element['#machine_name']['replace'] . '+$@', $element['#value'])) {
196
      $form_state->setError($element, t('The machine-readable name must contain unique characters.'));
197 198 199 200 201 202 203 204
    }

    // Verify that the machine name contains no disallowed characters.
    if (preg_match('@' . $element['#machine_name']['replace_pattern'] . '@', $element['#value'])) {
      if (!isset($element['#machine_name']['error'])) {
        // Since a hyphen is the most common alternative replacement character,
        // a corresponding validation error message is supported here.
        if ($element['#machine_name']['replace'] == '-') {
205
          $form_state->setError($element, t('The machine-readable name must contain only lowercase letters, numbers, and hyphens.'));
206 207 208
        }
        // Otherwise, we assume the default (underscore).
        else {
209
          $form_state->setError($element, t('The machine-readable name must contain only lowercase letters, numbers, and underscores.'));
210 211 212
        }
      }
      else {
213
        $form_state->setError($element, $element['#machine_name']['error']);
214 215 216 217 218 219 220
      }
    }

    // Verify that the machine name is unique.
    if ($element['#default_value'] !== $element['#value']) {
      $function = $element['#machine_name']['exists'];
      if (call_user_func($function, $element['#value'], $element, $form_state)) {
221
        $form_state->setError($element, t('The machine-readable name is already in use. It must be unique.'));
222 223 224 225 226
      }
    }
  }

}