1<?php
2
3namespace Drupal\Core\Render\Element;
4
5use Drupal\Core\Form\FormStateInterface;
6use Drupal\Core\Render\BubbleableMetadata;
7use Drupal\Core\Url;
8
9/**
10 * Provides a base class for form element plugins.
11 *
12 * Form elements are a subset of render elements, representing elements for
13 * HTML forms, which can be referenced in form arrays. See the
14 * @link theme_render Render API topic @endlink for an overview of render
15 * arrays and render elements, and the @link form_api Form API topic @endlink
16 * for an overview of forms and form arrays.
17 *
18 * The elements of form arrays are divided up into properties (whose keys
19 * start with #) and children (whose keys do not start with #). The properties
20 * provide data or settings that are used in rendering and form processing.
21 * Some properties are specific to a particular type of form/render element,
22 * some are available for any render element, and some are available for any
23 * form input element. A list of the properties that are available for all form
24 * elements follows; see \Drupal\Core\Render\Element\RenderElement for some
25 * additional information, as well as a list of properties that are common to
26 * all render elements (including form elements). Properties specific to a
27 * particular element are documented on that element's class.
28 *
29 * Here is a list of properties that are used during the rendering and form
30 * processing of form elements:
31 * - #after_build: (array) Array of callables or function names, which are
32 *   called after the element is built. Arguments: $element, $form_state.
33 * - #ajax: (array) Array of elements to specify Ajax behavior. See
34 *   the @link ajax Ajax API topic @endlink for more information.
35 * - #array_parents: (string[], read-only) Array of names of all the element's
36 *   parents (including itself) in the render array. See also #parents, #tree.
37 * - #default_value: Default value for the element. See also #value.
38 * - #description: (string) Help or description text for the element. In an
39 *   ideal user interface, the #title should be enough to describe the element,
40 *   so most elements should not have a description; if you do need one, make
41 *   sure it is translated. If it is not already wrapped in a safe markup
42 *   object, it will be filtered for XSS safety.
43 * - #disabled: (bool) If TRUE, the element is shown but does not accept
44 *   user input.
45 * - #element_validate: (array) Array of callables or function names, which
46 *   are called to validate the input. Arguments: $element, $form_state, $form.
47 * - #field_prefix: (string) Prefix to display before the HTML input element.
48 *   Should be translated, normally. If it is not already wrapped in a safe
49 *   markup object, will be filtered for XSS safety.
50 * - #field_suffix: (string) Suffix to display after the HTML input element.
51 *   Should be translated, normally. If it is not already wrapped in a safe
52 *   markup object, will be filtered for XSS safety.
53 * - #input: (bool, internal) Whether or not the element accepts input.
54 * - #parents: (string[], read-only) Array of names of the element's parents
55 *   for purposes of getting values out of $form_state. See also
56 *   #array_parents, #tree.
57 * - #process: (array) Array of callables or function names, which are
58 *   called during form building. Arguments: $element, $form_state, $form.
59 * - #processed: (bool, internal) Set to TRUE when the element is processed.
60 * - #required: (bool) Whether or not input is required on the element.
61 * - #states: (array) Information about JavaScript states, such as when to
62 *   hide or show the element based on input on other elements.
63 *   See \Drupal\Core\Form\FormHelper::processStates() for documentation.
64 * - #title: (string) Title of the form element. Should be translated.
65 * - #title_display: (string) Where and how to display the #title. Possible
66 *   values:
67 *   - before: Label goes before the element (default for most elements).
68 *   - after: Label goes after the element (default for radio elements).
69 *   - invisible: Label is there but is made invisible using CSS.
70 *   - attribute: Make it the title attribute (hover tooltip).
71 * - #tree: (bool) TRUE if the values of this element and its children should
72 *   be hierarchical in $form_state; FALSE if the values should be flat.
73 *   See also #parents, #array_parents.
74 * - #value_callback: (callable) Callable or function name, which is called
75 *   to transform the raw user input to the element's value. Arguments:
76 *   $element, $input, $form_state.
77 *
78 * @see \Drupal\Core\Render\Annotation\FormElement
79 * @see \Drupal\Core\Render\Element\FormElementInterface
80 * @see \Drupal\Core\Render\ElementInfoManager
81 * @see plugin_api
82 *
83 * @ingroup theme_render
84 */
85abstract class FormElement extends RenderElement implements FormElementInterface {
86
87  /**
88   * {@inheritdoc}
89   */
90  public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
91    return NULL;
92  }
93
94  /**
95   * #process callback for #pattern form element property.
96   *
97   * @param array $element
98   *   An associative array containing the properties and children of the
99   *   generic input element.
100   * @param \Drupal\Core\Form\FormStateInterface $form_state
101   *   The current state of the form.
102   * @param array $complete_form
103   *   The complete form structure.
104   *
105   * @return array
106   *   The processed element.
107   */
108  public static function processPattern(&$element, FormStateInterface $form_state, &$complete_form) {
109    if (isset($element['#pattern']) && !isset($element['#attributes']['pattern'])) {
110      $element['#attributes']['pattern'] = $element['#pattern'];
111      $element['#element_validate'][] = [get_called_class(), 'validatePattern'];
112    }
113
114    return $element;
115  }
116
117  /**
118   * #element_validate callback for #pattern form element property.
119   *
120   * @param $element
121   *   An associative array containing the properties and children of the
122   *   generic form element.
123   * @param $form_state
124   *   The current state of the form.
125   * @param array $complete_form
126   *   The complete form structure.
127   */
128  public static function validatePattern(&$element, FormStateInterface $form_state, &$complete_form) {
129    if ($element['#value'] !== '') {
130      // The pattern must match the entire string and should have the same
131      // behavior as the RegExp object in ECMA 262.
132      // - Use bracket-style delimiters to avoid introducing a special delimiter
133      //   character like '/' that would have to be escaped.
134      // - Put in brackets so that the pattern can't interfere with what's
135      //   prepended and appended.
136      $pattern = '{^(?:' . $element['#pattern'] . ')$}';
137
138      if (!preg_match($pattern, $element['#value'])) {
139        $form_state->setError($element, t('%name field is not in the right format.', ['%name' => $element['#title']]));
140      }
141    }
142  }
143
144  /**
145   * Adds autocomplete functionality to elements.
146   *
147   * This sets up autocomplete functionality for elements with an
148   * #autocomplete_route_name property, using the #autocomplete_route_parameters
149   * property if present.
150   *
151   * For example, suppose your autocomplete route name is
152   * 'mymodule.autocomplete' and its path is
153   * '/mymodule/autocomplete/{a}/{b}'. In a form array, you would create a text
154   * field with properties:
155   * @code
156   * '#autocomplete_route_name' => 'mymodule.autocomplete',
157   * '#autocomplete_route_parameters' => array('a' => $some_key, 'b' => $some_id),
158   * @endcode
159   * If the user types "keywords" in that field, the full path called would be:
160   * 'mymodule_autocomplete/$some_key/$some_id?q=keywords'
161   *
162   * @param array $element
163   *   The form element to process. Properties used:
164   *   - #autocomplete_route_name: A route to be used as callback URL by the
165   *     autocomplete JavaScript library.
166   *   - #autocomplete_route_parameters: The parameters to be used in
167   *     conjunction with the route name.
168   * @param \Drupal\Core\Form\FormStateInterface $form_state
169   *   The current state of the form.
170   * @param array $complete_form
171   *   The complete form structure.
172   *
173   * @return array
174   *   The form element.
175   */
176  public static function processAutocomplete(&$element, FormStateInterface $form_state, &$complete_form) {
177    $url = NULL;
178    $access = FALSE;
179
180    if (!empty($element['#autocomplete_route_name'])) {
181      $parameters = isset($element['#autocomplete_route_parameters']) ? $element['#autocomplete_route_parameters'] : [];
182      $url = Url::fromRoute($element['#autocomplete_route_name'], $parameters)->toString(TRUE);
183      /** @var \Drupal\Core\Access\AccessManagerInterface $access_manager */
184      $access_manager = \Drupal::service('access_manager');
185      $access = $access_manager->checkNamedRoute($element['#autocomplete_route_name'], $parameters, \Drupal::currentUser(), TRUE);
186    }
187
188    if ($access) {
189      $metadata = BubbleableMetadata::createFromRenderArray($element);
190      if ($access->isAllowed()) {
191        $element['#attributes']['class'][] = 'form-autocomplete';
192        $metadata->addAttachments(['library' => ['core/drupal.autocomplete']]);
193        // Provide a data attribute for the JavaScript behavior to bind to.
194        $element['#attributes']['data-autocomplete-path'] = $url->getGeneratedUrl();
195        $metadata = $metadata->merge($url);
196      }
197      $metadata
198        ->merge(BubbleableMetadata::createFromObject($access))
199        ->applyTo($element);
200    }
201
202    return $element;
203  }
204
205}
206