form.inc

  1. 7.x drupal/includes/form.inc
  2. 5.x drupal/includes/form.inc
  3. 6.x drupal/includes/form.inc
  4. 8.x drupal/core/includes/form.inc

Functions for form and batch generation and processing.

Functions

Namesort descending Description
batch_get Retrieves the current batch.
batch_process Processes the batch.
batch_set Adds a new batch.
drupal_form_submit Deprecated Retrieves, populates, and processes a form.
drupal_process_form Deprecated Processes a form submission.
drupal_redirect_form Deprecated Redirects the user to a URL after a form has been processed.
drupal_retrieve_form Deprecated Retrieves the structured array that defines a given form.
form_builder Deprecated Builds and processes all elements in the structured form array.
form_error Deprecated Flags an element as having an error.
form_execute_handlers Deprecated Executes custom validation and submission handlers for a given form.
form_get_cache Deprecated Fetches a form from the cache.
form_get_error Deprecated Returns the error message filed against the given form element.
form_get_errors Deprecated Returns an associative array of all errors.
form_get_options Returns the indexes of a select element's options matching a given key.
form_load_include Ensures an include file is loaded whenever the form is processed.
form_options_flatten Deprecated Allows PHP array processing of multiple select options with the same value.
form_pre_render_actions_dropbutton #pre_render callback for #type 'actions'.
form_pre_render_button Prepares a #type 'button' render element for theme_input().
form_pre_render_checkbox Prepares a #type 'checkbox' render element for theme_input().
form_pre_render_color Prepares a #type 'color' render element for theme_input().
form_pre_render_conditional_form_element Adds form element theming to an element if its title or description is set.
form_pre_render_date Adds form-specific attributes to a 'date' #type element.
form_pre_render_details Adds form element theming to details.
form_pre_render_email Prepares a #type 'email' render element for theme_input().
form_pre_render_file Prepares a #type 'file' render element for theme_input().
form_pre_render_group Adds members of this group as actual elements for rendering.
form_pre_render_hidden Prepares a #type 'hidden' render element for theme_input().
form_pre_render_image_button Prepares a #type 'image_button' render element for theme_input().
form_pre_render_number Prepares a #type 'number' render element for theme_input().
form_pre_render_password Prepares a #type 'password' render element for theme_input().
form_pre_render_radio Prepares a #type 'radio' render element for theme_input().
form_pre_render_range Prepares a #type 'range' render element for theme_input().
form_pre_render_search Prepares a #type 'search' render element for theme_input().
form_pre_render_tel Prepares a #type 'tel' render element for theme_input().
form_pre_render_textfield Prepares a #type 'textfield' render element for theme_input().
form_pre_render_url Prepares a #type 'url' render element for theme_input().
form_pre_render_vertical_tabs Prepares a vertical_tabs element for rendering.
form_process_actions Processes a form actions container element.
form_process_autocomplete Adds autocomplete functionality to elements with a valid #autocomplete_route_name.
form_process_button Processes a form button element.
form_process_checkbox Sets the #checked property of a checkbox element.
form_process_checkboxes Processes a checkboxes form element.
form_process_container Processes a container element.
form_process_file Processes a file upload element, make use of #multiple if present.
form_process_group Arranges elements into groups.
form_process_machine_name Processes a machine-readable name form element.
form_process_password_confirm Expand a password_confirm field into two text boxes.
form_process_pattern #process callback for #pattern form element property.
form_process_radios Expands a radios element into individual radio elements.
form_process_select Processes a select list form element.
form_process_table #process callback for #type 'table' to add tableselect support.
form_process_tableselect Creates checkbox or radio elements to populate a tableselect table.
form_process_vertical_tabs Creates a group formatted as vertical tabs.
form_process_weight Expands a weight element into a select element.
form_select_options Converts a select form element's options array into HTML.
form_set_cache Deprecated Stores a form in the cache.
form_set_error Deprecated Files an error against a form element.
form_set_value Deprecated Changes submitted form values during form validation.
form_state_values_clean Removes internal Form API elements and buttons from submitted form values.
form_type_checkboxes_value Determines the value for a checkboxes form element.
form_type_checkbox_value Determines the value for a checkbox form element.
form_type_image_button_value Determines the value for an image button form element.
form_type_password_confirm_value Determines the value for a password_confirm form element.
form_type_radios_value Form value callback: Determines the value for a #type radios form element.
form_type_range_value Determines the value for a range element.
form_type_select_value Determines the value for a select form element.
form_type_tableselect_value Determines the value for a tableselect form element.
form_type_table_value Determines the value of a table form element.
form_type_textfield_value Determines the value for a textfield form element.
form_type_token_value Determines the value for form's token value.
form_validate_color Form element validation handler for #type 'color'.
form_validate_email Form element validation handler for #type 'email'.
form_validate_machine_name Form element validation handler for machine_name elements.
form_validate_number Form element validation handler for #type 'number'.
form_validate_pattern #element_validate callback for #pattern form element property.
form_validate_table #element_validate callback for #type 'table'.
form_validate_url Form element validation handler for #type 'url'.
password_confirm_validate Validates a password_confirm element.
template_preprocess_checkboxes Prepares variables for checkboxes templates.
template_preprocess_details Prepares variables for details element templates.
template_preprocess_fieldset Prepares variables for fieldset element templates.
template_preprocess_form Prepares variables for form templates.
template_preprocess_form_element Returns HTML for a form element. Prepares variables for form element templates.
template_preprocess_form_element_label Prepares variables for form label templates.
template_preprocess_input Prepares variables for input templates.
template_preprocess_radios Prepares variables for radios templates.
template_preprocess_select Prepares variables for select element templates.
template_preprocess_textarea Prepares variables for textarea templates.
template_preprocess_vertical_tabs Prepares variables for vertical tabs templates.
theme_tableselect Returns HTML for a table with radio buttons or checkboxes.
weight_value Sets the value for a weight element, with zero as a default.
_batch_populate_queue Populates a job queue with the operations of a batch set.
_batch_queue Returns a queue object for a batch set.
_form_set_attributes Sets a form element's class attribute.

File

drupal/core/includes/form.inc
View source
  1. <?php
  2. /**
  3. * @file
  4. * Functions for form and batch generation and processing.
  5. */
  6. use Drupal\Component\Utility\NestedArray;
  7. use Drupal\Component\Utility\Number;
  8. use Drupal\Component\Utility\SafeMarkup;
  9. use Drupal\Component\Utility\String;
  10. use Drupal\Component\Utility\UrlHelper;
  11. use Drupal\Component\Utility\Xss;
  12. use Drupal\Core\Database\Database;
  13. use Drupal\Core\Form\OptGroup;
  14. use Drupal\Core\Render\Element;
  15. use Drupal\Core\Template\Attribute;
  16. use Drupal\Core\Utility\Color;
  17. use Symfony\Component\HttpFoundation\RedirectResponse;
  18. /**
  19. * @defgroup form_api Form generation
  20. * @{
  21. * Describes how to generate and manipulate forms and process form submissions.
  22. *
  23. * Drupal provides a Form API in order to achieve consistency in its form
  24. * processing and presentation, while simplifying code and reducing the amount
  25. * of HTML that must be explicitly generated by a module.
  26. *
  27. * @section generating_forms Creating forms
  28. * Forms are defined as classes that implement the
  29. * \Drupal\Core\Form\FormInterface and are built using the
  30. * \Drupal\Core\Form\FormBuilder class. Drupal provides a couple of utility
  31. * classes that can be extended as a starting point for most basic forms, the
  32. * most commonly used of which is \Drupal\Core\Form\FormBase. FormBuilder
  33. * handles the low level processing of forms such as rendering the necessary
  34. * HTML, initial processing of incoming $_POST data, and delegating to your
  35. * implementation of FormInterface for validation and processing of submitted
  36. * data.
  37. *
  38. * Here is an example of a Form class:
  39. * @code
  40. * namespace Drupal\mymodule\Form;
  41. *
  42. * use Drupal\Core\Form\FormBase;
  43. *
  44. * class ExampleForm extends FormBase {
  45. * public function getFormId() {
  46. * // Unique ID of the form.
  47. * return 'example_form';
  48. * }
  49. *
  50. * public function buildForm(array $form, array &$form_state) {
  51. * // Create a $form API array.
  52. * $form['phone_number'] = array(
  53. * '#type' => 'tel',
  54. * '#title' => $this->t('Your phone number')
  55. * );
  56. * return $form;
  57. * }
  58. *
  59. * public function validateForm(array &$form, array &$form_state) {
  60. * // Validate submitted form data.
  61. * }
  62. *
  63. * public function submitForm(array &$form, array &$form_state) {
  64. * // Handle submitted form data.
  65. * }
  66. * }
  67. * @endcode
  68. *
  69. * @section retrieving_forms Retrieving and displaying forms
  70. * \Drupal::formBuilder()->getForm() should be used to handle retrieving,
  71. * processing, and displaying a rendered HTML form. Given the ExampleForm
  72. * defined above,
  73. * \Drupal::formBuilder()->getForm('Drupal\mymodule\Form\ExampleForm') would
  74. * return the rendered HTML of the form defined by ExampleForm::buildForm(), or
  75. * call the validateForm() and submitForm(), methods depending on the current
  76. * processing state.
  77. *
  78. * The argument to \Drupal::formBuilder()->getForm() is the name of a class that
  79. * implements FormBuilderInterface. Any additional arguments passed to the
  80. * getForm() method will be passed along as additional arguments to the
  81. * ExampleForm::buildForm() method.
  82. *
  83. * For example:
  84. * @code
  85. * $extra = '612-123-4567';
  86. * $form = \Drupal::formBuilder()->getForm('Drupal\mymodule\Form\ExampleForm', $extra);
  87. * ...
  88. * public function buildForm(array $form, array &$form_state, $extra = NULL)
  89. * $form['phone_number'] = array(
  90. * '#type' => 'tel',
  91. * '#title' => $this->t('Your phone number'),
  92. * '#value' => $extra,
  93. * );
  94. * return $form;
  95. * }
  96. * @endcode
  97. *
  98. * Alternatively, forms can be built directly via the routing system which will
  99. * take care of calling \Drupal::formBuilder()->getForm(). The following example
  100. * demonstrates the use of a routing.yml file to display a form at the the
  101. * given route.
  102. *
  103. * @code
  104. * example.form:
  105. * path: '/example-form'
  106. * defaults:
  107. * _title: 'Example form'
  108. * _form: '\Drupal\mymodule\Form\ExampleForm'
  109. * @endcode
  110. *
  111. * The $form argument to form-related functions is a structured array containing
  112. * the elements and properties of the form. For information on the array
  113. * components and format, and more detailed explanations of the Form API
  114. * workflow, see the
  115. * @link forms_api_reference.html Form API reference @endlink
  116. * and the
  117. * @link https://drupal.org/node/2117411 Form API documentation section. @endlink
  118. * In addition, there is a set of Form API tutorials in
  119. * @link form_example_tutorial.inc the Form Example Tutorial @endlink which
  120. * provide basics all the way up through multistep forms.
  121. *
  122. * In the form builder, validation, submission, and other form methods,
  123. * $form_state is the primary influence on the processing of the form and is
  124. * passed by reference to most methods, so they can use it to communicate with
  125. * the form system and each other.
  126. *
  127. * See \Drupal\Core\Form\FormBuilder::buildForm() for documentation of
  128. * $form_state keys.
  129. */
  130. /**
  131. * Fetches a form from the cache.
  132. *
  133. * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
  134. * Use \Drupal::formBuilder()->getCache().
  135. *
  136. * @see \Drupal\Core\Form\FormBuilderInterface::getCache().
  137. */
  138. function form_get_cache($form_build_id, &$form_state) {
  139. return \Drupal::formBuilder()->getCache($form_build_id, $form_state);
  140. }
  141. /**
  142. * Stores a form in the cache.
  143. *
  144. * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
  145. * Use \Drupal::formBuilder()->setCache().
  146. *
  147. * @see \Drupal\Core\Form\FormBuilderInterface::setCache().
  148. */
  149. function form_set_cache($form_build_id, $form, $form_state) {
  150. \Drupal::formBuilder()->setCache($form_build_id, $form, $form_state);
  151. }
  152. /**
  153. * Ensures an include file is loaded whenever the form is processed.
  154. *
  155. * Example:
  156. * @code
  157. * // Load node.admin.inc from Node module.
  158. * form_load_include($form_state, 'inc', 'node', 'node.admin');
  159. * @endcode
  160. *
  161. * Use this function instead of module_load_include() from inside a form
  162. * constructor or any form processing logic as it ensures that the include file
  163. * is loaded whenever the form is processed. In contrast to using
  164. * module_load_include() directly, form_load_include() makes sure the include
  165. * file is correctly loaded also if the form is cached.
  166. *
  167. * @param $form_state
  168. * The current state of the form.
  169. * @param $type
  170. * The include file's type (file extension).
  171. * @param $module
  172. * The module to which the include file belongs.
  173. * @param $name
  174. * (optional) The base file name (without the $type extension). If omitted,
  175. * $module is used; i.e., resulting in "$module.$type" by default.
  176. *
  177. * @return
  178. * The filepath of the loaded include file, or FALSE if the include file was
  179. * not found or has been loaded already.
  180. *
  181. * @see module_load_include()
  182. */
  183. function form_load_include(&$form_state, $type, $module, $name = NULL) {
  184. if (!isset($name)) {
  185. $name = $module;
  186. }
  187. if (!isset($form_state['build_info']['files']["$module:$name.$type"])) {
  188. // Only add successfully included files to the form state.
  189. if ($result = module_load_include($type, $module, $name)) {
  190. $form_state['build_info']['files']["$module:$name.$type"] = array(
  191. 'type' => $type,
  192. 'module' => $module,
  193. 'name' => $name,
  194. );
  195. return $result;
  196. }
  197. }
  198. return FALSE;
  199. }
  200. /**
  201. * Retrieves, populates, and processes a form.
  202. *
  203. * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
  204. * Use \Drupal::formBuilder()->submitForm().
  205. *
  206. * @see \Drupal\Core\Form\FormBuilderInterface::submitForm().
  207. */
  208. function drupal_form_submit($form_arg, &$form_state) {
  209. \Drupal::formBuilder()->submitForm($form_arg, $form_state);
  210. }
  211. /**
  212. * Retrieves the structured array that defines a given form.
  213. *
  214. * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
  215. * Use \Drupal::formBuilder()->retrieveForm().
  216. *
  217. * @see \Drupal\Core\Form\FormBuilderInterface::retrieveForm().
  218. */
  219. function drupal_retrieve_form($form_id, &$form_state) {
  220. return \Drupal::formBuilder()->retrieveForm($form_id, $form_state);
  221. }
  222. /**
  223. * Processes a form submission.
  224. *
  225. * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
  226. * Use \Drupal::formBuilder()->processForm().
  227. *
  228. * @see \Drupal\Core\Form\FormBuilderInterface::processForm().
  229. */
  230. function drupal_process_form($form_id, &$form, &$form_state) {
  231. \Drupal::formBuilder()->processForm($form_id, $form, $form_state);
  232. }
  233. /**
  234. * Redirects the user to a URL after a form has been processed.
  235. *
  236. * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
  237. * Use \Drupal::service('form_submitter')->redirectForm().
  238. *
  239. * @see \Drupal\Core\Form\FormSubmitterInterface::redirectForm().
  240. */
  241. function drupal_redirect_form($form_state) {
  242. return \Drupal::service('form_submitter')->redirectForm($form_state);
  243. }
  244. /**
  245. * Executes custom validation and submission handlers for a given form.
  246. *
  247. * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
  248. * Use either \Drupal::service('form_submitter')->executeSubmitHandlers() or
  249. * \Drupal::service('form_validator')->executeValidateHandlers().
  250. *
  251. * @see \Drupal\Core\Form\FormSubmitterInterface::executeSubmitHandlers()
  252. * @see \Drupal\Core\Form\FormValidatorInterface::executeValidateHandlers()
  253. */
  254. function form_execute_handlers($type, &$form, &$form_state) {
  255. if ($type == 'submit') {
  256. \Drupal::service('form_submitter')->executeSubmitHandlers($form, $form_state);
  257. }
  258. elseif ($type == 'validate') {
  259. \Drupal::service('form_validator')->executeValidateHandlers($form, $form_state);
  260. }
  261. }
  262. /**
  263. * Files an error against a form element.
  264. *
  265. * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
  266. * Use \Drupal::formBuilder()->setErrorByName().
  267. *
  268. * @see \Drupal\Core\Form\FormErrorInterface::setErrorByName().
  269. */
  270. function form_set_error($name, array &$form_state, $message = '') {
  271. \Drupal::formBuilder()->setErrorByName($name, $form_state, $message);
  272. }
  273. /**
  274. * Returns an associative array of all errors.
  275. *
  276. * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
  277. * Use \Drupal::formBuilder()->getErrors().
  278. *
  279. * @see \Drupal\Core\Form\FormErrorInterface::getErrors()
  280. */
  281. function form_get_errors(array &$form_state) {
  282. return \Drupal::formBuilder()->getErrors($form_state);
  283. }
  284. /**
  285. * Returns the error message filed against the given form element.
  286. *
  287. * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
  288. * Use \Drupal::formBuilder()->getError().
  289. *
  290. * @see \Drupal\Core\Form\FormErrorInterface::getError().
  291. */
  292. function form_get_error($element, array &$form_state) {
  293. return \Drupal::formBuilder()->getError($element, $form_state);
  294. }
  295. /**
  296. * Flags an element as having an error.
  297. *
  298. * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
  299. * Use \Drupal::formBuilder()->setError().
  300. *
  301. * @see \Drupal\Core\Form\FormErrorInterface::setError().
  302. */
  303. function form_error(&$element, array &$form_state, $message = '') {
  304. \Drupal::formBuilder()->setError($element, $form_state, $message);
  305. }
  306. /**
  307. * Builds and processes all elements in the structured form array.
  308. *
  309. * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
  310. * Use \Drupal::formBuilder()->doBuildForm().
  311. *
  312. * @see \Drupal\Core\Form\FormBuilderInterface::doBuildForm().
  313. */
  314. function form_builder($form_id, &$element, &$form_state) {
  315. return \Drupal::formBuilder()->doBuildForm($form_id, $element, $form_state);
  316. }
  317. /**
  318. * Removes internal Form API elements and buttons from submitted form values.
  319. *
  320. * This function can be used when a module wants to store all submitted form
  321. * values, for example, by serializing them into a single database column. In
  322. * such cases, all internal Form API values and all form button elements should
  323. * not be contained, and this function allows to remove them before the module
  324. * proceeds to storage. Next to button elements, the following internal values
  325. * are removed:
  326. * - form_id
  327. * - form_token
  328. * - form_build_id
  329. * - op
  330. *
  331. * @param $form_state
  332. * A keyed array containing the current state of the form, including
  333. * submitted form values; altered by reference.
  334. */
  335. function form_state_values_clean(&$form_state) {
  336. // Remove internal Form API values.
  337. unset($form_state['values']['form_id'], $form_state['values']['form_token'], $form_state['values']['form_build_id'], $form_state['values']['op']);
  338. // Remove button values.
  339. // form_builder() collects all button elements in a form. We remove the button
  340. // value separately for each button element.
  341. foreach ($form_state['buttons'] as $button) {
  342. // Remove this button's value from the submitted form values by finding
  343. // the value corresponding to this button.
  344. // We iterate over the #parents of this button and move a reference to
  345. // each parent in $form_state['values']. For example, if #parents is:
  346. // array('foo', 'bar', 'baz')
  347. // then the corresponding $form_state['values'] part will look like this:
  348. // array(
  349. // 'foo' => array(
  350. // 'bar' => array(
  351. // 'baz' => 'button_value',
  352. // ),
  353. // ),
  354. // )
  355. // We start by (re)moving 'baz' to $last_parent, so we are able unset it
  356. // at the end of the iteration. Initially, $values will contain a
  357. // reference to $form_state['values'], but in the iteration we move the
  358. // reference to $form_state['values']['foo'], and finally to
  359. // $form_state['values']['foo']['bar'], which is the level where we can
  360. // unset 'baz' (that is stored in $last_parent).
  361. $parents = $button['#parents'];
  362. $last_parent = array_pop($parents);
  363. $key_exists = NULL;
  364. $values = &NestedArray::getValue($form_state['values'], $parents, $key_exists);
  365. if ($key_exists && is_array($values)) {
  366. unset($values[$last_parent]);
  367. }
  368. }
  369. }
  370. /**
  371. * Determines the value for an image button form element.
  372. *
  373. * @param $form
  374. * The form element whose value is being populated.
  375. * @param $input
  376. * The incoming input to populate the form element. If this is FALSE,
  377. * the element's default value should be returned.
  378. * @param $form_state
  379. * A keyed array containing the current state of the form.
  380. *
  381. * @return
  382. * The data that will appear in the $form_state['values'] collection
  383. * for this element. Return nothing to use the default.
  384. */
  385. function form_type_image_button_value($form, $input, $form_state) {
  386. if ($input !== FALSE) {
  387. if (!empty($input)) {
  388. // If we're dealing with Mozilla or Opera, we're lucky. It will
  389. // return a proper value, and we can get on with things.
  390. return $form['#return_value'];
  391. }
  392. else {
  393. // Unfortunately, in IE we never get back a proper value for THIS
  394. // form element. Instead, we get back two split values: one for the
  395. // X and one for the Y coordinates on which the user clicked the
  396. // button. We'll find this element in the #post data, and search
  397. // in the same spot for its name, with '_x'.
  398. $input = $form_state['input'];
  399. foreach (explode('[', $form['#name']) as $element_name) {
  400. // chop off the ] that may exist.
  401. if (substr($element_name, -1) == ']') {
  402. $element_name = substr($element_name, 0, -1);
  403. }
  404. if (!isset($input[$element_name])) {
  405. if (isset($input[$element_name . '_x'])) {
  406. return $form['#return_value'];
  407. }
  408. return NULL;
  409. }
  410. $input = $input[$element_name];
  411. }
  412. return $form['#return_value'];
  413. }
  414. }
  415. }
  416. /**
  417. * Determines the value for a checkbox form element.
  418. *
  419. * @param $form
  420. * The form element whose value is being populated.
  421. * @param $input
  422. * The incoming input to populate the form element. If this is FALSE,
  423. * the element's default value should be returned.
  424. *
  425. * @return
  426. * The data that will appear in the $element_state['values'] collection
  427. * for this element. Return nothing to use the default.
  428. */
  429. function form_type_checkbox_value($element, $input = FALSE) {
  430. if ($input === FALSE) {
  431. // Use #default_value as the default value of a checkbox, except change
  432. // NULL to 0, because FormBuilder::handleInputElement() would otherwise
  433. // replace NULL with empty string, but an empty string is a potentially
  434. // valid value for a checked checkbox.
  435. return isset($element['#default_value']) ? $element['#default_value'] : 0;
  436. }
  437. else {
  438. // Checked checkboxes are submitted with a value (possibly '0' or ''):
  439. // http://www.w3.org/TR/html401/interact/forms.html#successful-controls.
  440. // For checked checkboxes, browsers submit the string version of
  441. // #return_value, but we return the original #return_value. For unchecked
  442. // checkboxes, browsers submit nothing at all, but
  443. // FormBuilder::handleInputElement() detects this, and calls this
  444. // function with $input=NULL. Returning NULL from a value callback means to
  445. // use the default value, which is not what is wanted when an unchecked
  446. // checkbox is submitted, so we use integer 0 as the value indicating an
  447. // unchecked checkbox. Therefore, modules must not use integer 0 as a
  448. // #return_value, as doing so results in the checkbox always being treated
  449. // as unchecked. The string '0' is allowed for #return_value. The most
  450. // common use-case for setting #return_value to either 0 or '0' is for the
  451. // first option within a 0-indexed array of checkboxes, and for this,
  452. // form_process_checkboxes() uses the string rather than the integer.
  453. return isset($input) ? $element['#return_value'] : 0;
  454. }
  455. }
  456. /**
  457. * Determines the value for a checkboxes form element.
  458. *
  459. * @param $element
  460. * The form element whose value is being populated.
  461. * @param $input
  462. * The incoming input to populate the form element. If this is FALSE,
  463. * the element's default value should be returned.
  464. *
  465. * @return
  466. * The data that will appear in the $element_state['values'] collection
  467. * for this element. Return nothing to use the default.
  468. */
  469. function form_type_checkboxes_value($element, $input = FALSE) {
  470. if ($input === FALSE) {
  471. $value = array();
  472. $element += array('#default_value' => array());
  473. foreach ($element['#default_value'] as $key) {
  474. $value[$key] = $key;
  475. }
  476. return $value;
  477. }
  478. elseif (is_array($input)) {
  479. // Programmatic form submissions use NULL to indicate that a checkbox
  480. // should be unchecked; see drupal_form_submit(). We therefore remove all
  481. // NULL elements from the array before constructing the return value, to
  482. // simulate the behavior of web browsers (which do not send unchecked
  483. // checkboxes to the server at all). This will not affect non-programmatic
  484. // form submissions, since all values in \Drupal::request()->request are
  485. // strings.
  486. foreach ($input as $key => $value) {
  487. if (!isset($value)) {
  488. unset($input[$key]);
  489. }
  490. }
  491. return array_combine($input, $input);
  492. }
  493. else {
  494. return array();
  495. }
  496. }
  497. /**
  498. * Determines the value of a table form element.
  499. *
  500. * @param array $element
  501. * The form element whose value is being populated.
  502. * @param array|false $input
  503. * The incoming input to populate the form element. If this is FALSE,
  504. * the element's default value should be returned.
  505. *
  506. * @return array
  507. * The data that will appear in the $form_state['values'] collection
  508. * for this element. Return nothing to use the default.
  509. */
  510. function form_type_table_value(array $element, $input = FALSE) {
  511. // If #multiple is FALSE, the regular default value of radio buttons is used.
  512. if (!empty($element['#tableselect']) && !empty($element['#multiple'])) {
  513. // Contrary to #type 'checkboxes', the default value of checkboxes in a
  514. // table is built from the array keys (instead of array values) of the
  515. // #default_value property.
  516. // @todo D8: Remove this inconsistency.
  517. if ($input === FALSE) {
  518. $element += array('#default_value' => array());
  519. $value = array_keys(array_filter($element['#default_value']));
  520. return array_combine($value, $value);
  521. }
  522. else {
  523. return is_array($input) ? array_combine($input, $input) : array();
  524. }
  525. }
  526. }
  527. /**
  528. * Form value callback: Determines the value for a #type radios form element.
  529. *
  530. * @param $element
  531. * The form element whose value is being populated.
  532. * @param $input
  533. * (optional) The incoming input to populate the form element. If FALSE, the
  534. * element's default value is returned. Defaults to FALSE.
  535. *
  536. * @return
  537. * The data that will appear in the $element_state['values'] collection for
  538. * this element.
  539. */
  540. function form_type_radios_value(&$element, $input = FALSE) {
  541. if ($input !== FALSE) {
  542. // When there's user input (including NULL), return it as the value.
  543. // However, if NULL is submitted, FormBuilder::handleInputElement() will
  544. // apply the default value, and we want that validated against #options
  545. // unless it's empty. (An empty #default_value, such as NULL or FALSE, can
  546. // be used to indicate that no radio button is selected by default.)
  547. if (!isset($input) && !empty($element['#default_value'])) {
  548. $element['#needs_validation'] = TRUE;
  549. }
  550. return $input;
  551. }
  552. else {
  553. // For default value handling, simply return #default_value. Additionally,
  554. // for a NULL default value, set #has_garbage_value to prevent
  555. // FormBuilder::handleInputElement() converting the NULL to an empty
  556. // string, so that code can distinguish between nothing selected and the
  557. // selection of a radio button whose value is an empty string.
  558. $value = isset($element['#default_value']) ? $element['#default_value'] : NULL;
  559. if (!isset($value)) {
  560. $element['#has_garbage_value'] = TRUE;
  561. }
  562. return $value;
  563. }
  564. }
  565. /**
  566. * Determines the value for a tableselect form element.
  567. *
  568. * @param $element
  569. * The form element whose value is being populated.
  570. * @param $input
  571. * The incoming input to populate the form element. If this is FALSE,
  572. * the element's default value should be returned.
  573. *
  574. * @return
  575. * The data that will appear in the $element_state['values'] collection
  576. * for this element. Return nothing to use the default.
  577. */
  578. function form_type_tableselect_value($element, $input = FALSE) {
  579. // If $element['#multiple'] == FALSE, then radio buttons are displayed and
  580. // the default value handling is used.
  581. if (isset($element['#multiple']) && $element['#multiple']) {
  582. // Checkboxes are being displayed with the default value coming from the
  583. // keys of the #default_value property. This differs from the checkboxes
  584. // element which uses the array values.
  585. if ($input === FALSE) {
  586. $value = array();
  587. $element += array('#default_value' => array());
  588. foreach ($element['#default_value'] as $key => $flag) {
  589. if ($flag) {
  590. $value[$key] = $key;
  591. }
  592. }
  593. return $value;
  594. }
  595. else {
  596. return is_array($input) ? array_combine($input, $input) : array();
  597. }
  598. }
  599. }
  600. /**
  601. * Determines the value for a password_confirm form element.
  602. *
  603. * @param $element
  604. * The form element whose value is being populated.
  605. * @param $input
  606. * The incoming input to populate the form element. If this is FALSE,
  607. * the element's default value should be returned.
  608. *
  609. * @return
  610. * The data that will appear in the $element_state['values'] collection
  611. * for this element. Return nothing to use the default.
  612. */
  613. function form_type_password_confirm_value($element, $input = FALSE) {
  614. if ($input === FALSE) {
  615. $element += array('#default_value' => array());
  616. return $element['#default_value'] + array('pass1' => '', 'pass2' => '');
  617. }
  618. }
  619. /**
  620. * Determines the value for a select form element.
  621. *
  622. * @param $element
  623. * The form element whose value is being populated.
  624. * @param $input
  625. * The incoming input to populate the form element. If this is FALSE,
  626. * the element's default value should be returned.
  627. *
  628. * @return
  629. * The data that will appear in the $element_state['values'] collection
  630. * for this element. Return nothing to use the default.
  631. */
  632. function form_type_select_value($element, $input = FALSE) {
  633. if ($input !== FALSE) {
  634. if (isset($element['#multiple']) && $element['#multiple']) {
  635. // If an enabled multi-select submits NULL, it means all items are
  636. // unselected. A disabled multi-select always submits NULL, and the
  637. // default value should be used.
  638. if (empty($element['#disabled'])) {
  639. return (is_array($input)) ? array_combine($input, $input) : array();
  640. }
  641. else {
  642. return (isset($element['#default_value']) && is_array($element['#default_value'])) ? $element['#default_value'] : array();
  643. }
  644. }
  645. // Non-multiple select elements may have an empty option preprended to them
  646. // (see form_process_select()). When this occurs, usually #empty_value is
  647. // an empty string, but some forms set #empty_value to integer 0 or some
  648. // other non-string constant. PHP receives all submitted form input as
  649. // strings, but if the empty option is selected, set the value to match the
  650. // empty value exactly.
  651. elseif (isset($element['#empty_value']) && $input === (string) $element['#empty_value']) {
  652. return $element['#empty_value'];
  653. }
  654. else {
  655. return $input;
  656. }
  657. }
  658. }
  659. /**
  660. * Determines the value for a textfield form element.
  661. *
  662. * @param $element
  663. * The form element whose value is being populated.
  664. * @param $input
  665. * The incoming input to populate the form element. If this is FALSE,
  666. * the element's default value should be returned.
  667. *
  668. * @return
  669. * The data that will appear in the $element_state['values'] collection
  670. * for this element. Return nothing to use the default.
  671. */
  672. function form_type_textfield_value($element, $input = FALSE) {
  673. if ($input !== FALSE && $input !== NULL) {
  674. // Equate $input to the form value to ensure it's marked for
  675. // validation.
  676. return str_replace(array("\r", "\n"), '', $input);
  677. }
  678. }
  679. /**
  680. * Determines the value for form's token value.
  681. *
  682. * @param $element
  683. * The form element whose value is being populated.
  684. * @param $input
  685. * The incoming input to populate the form element. If this is FALSE,
  686. * the element's default value should be returned.
  687. *
  688. * @return
  689. * The data that will appear in the $element_state['values'] collection
  690. * for this element. Return nothing to use the default.
  691. */
  692. function form_type_token_value($element, $input = FALSE) {
  693. if ($input !== FALSE) {
  694. return (string) $input;
  695. }
  696. }
  697. /**
  698. * Changes submitted form values during form validation.
  699. *
  700. * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
  701. * Use \Drupal::formBuilder()->setValue().
  702. *
  703. * @see \Drupal\Core\Form\FormBuilderInterface::setValue().
  704. */
  705. function form_set_value($element, $value, &$form_state) {
  706. \Drupal::formBuilder()->setValue($element, $value, $form_state);
  707. }
  708. /**
  709. * Allows PHP array processing of multiple select options with the same value.
  710. *
  711. * Used for form select elements which need to validate HTML option groups
  712. * and multiple options which may return the same value. Associative PHP arrays
  713. * cannot handle these structures, since they share a common key.
  714. *
  715. * @param $array
  716. * The form options array to process.
  717. *
  718. * @return
  719. * An array with all hierarchical elements flattened to a single array.
  720. *
  721. * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
  722. * Use \Drupal\Core\Form\OptGroup::flattenOptions().
  723. */
  724. function form_options_flatten($array) {
  725. return OptGroup::flattenOptions($array);
  726. }
  727. /**
  728. * Processes a select list form element.
  729. *
  730. * This process callback is mandatory for select fields, since all user agents
  731. * automatically preselect the first available option of single (non-multiple)
  732. * select lists.
  733. *
  734. * @param $element
  735. * The form element to process. Properties used:
  736. * - #multiple: (optional) Indicates whether one or more options can be
  737. * selected. Defaults to FALSE.
  738. * - #default_value: Must be NULL or not set in case there is no value for the
  739. * element yet, in which case a first default option is inserted by default.
  740. * Whether this first option is a valid option depends on whether the field
  741. * is #required or not.
  742. * - #required: (optional) Whether the user needs to select an option (TRUE)
  743. * or not (FALSE). Defaults to FALSE.
  744. * - #empty_option: (optional) The label to show for the first default option.
  745. * By default, the label is automatically set to "- Please select -" for a
  746. * required field and "- None -" for an optional field.
  747. * - #empty_value: (optional) The value for the first default option, which is
  748. * used to determine whether the user submitted a value or not.
  749. * - If #required is TRUE, this defaults to '' (an empty string).
  750. * - If #required is not TRUE and this value isn't set, then no extra option
  751. * is added to the select control, leaving the control in a slightly
  752. * illogical state, because there's no way for the user to select nothing,
  753. * since all user agents automatically preselect the first available
  754. * option. But people are used to this being the behavior of select
  755. * controls.
  756. * @todo Address the above issue in Drupal 8.
  757. * - If #required is not TRUE and this value is set (most commonly to an
  758. * empty string), then an extra option (see #empty_option above)
  759. * representing a "non-selection" is added with this as its value.
  760. *
  761. * @see _form_validate()
  762. */
  763. function form_process_select($element) {
  764. // #multiple select fields need a special #name.
  765. if ($element['#multiple']) {
  766. $element['#attributes']['multiple'] = 'multiple';
  767. $element['#attributes']['name'] = $element['#name'] . '[]';
  768. }
  769. // A non-#multiple select needs special handling to prevent user agents from
  770. // preselecting the first option without intention. #multiple select lists do
  771. // not get an empty option, as it would not make sense, user interface-wise.
  772. else {
  773. // If the element is set to #required through #states, override the
  774. // element's #required setting.
  775. $required = isset($element['#states']['required']) ? TRUE : $element['#required'];
  776. // If the element is required and there is no #default_value, then add an
  777. // empty option that will fail validation, so that the user is required to
  778. // make a choice. Also, if there's a value for #empty_value or
  779. // #empty_option, then add an option that represents emptiness.
  780. if (($required && !isset($element['#default_value'])) || isset($element['#empty_value']) || isset($element['#empty_option'])) {
  781. $element += array(
  782. '#empty_value' => '',
  783. '#empty_option' => $required ? t('- Select -') : t('- None -'),
  784. );
  785. // The empty option is prepended to #options and purposively not merged
  786. // to prevent another option in #options mistakenly using the same value
  787. // as #empty_value.
  788. $empty_option = array($element['#empty_value'] => $element['#empty_option']);
  789. $element['#options'] = $empty_option + $element['#options'];
  790. }
  791. }
  792. return $element;
  793. }
  794. /**
  795. * Prepares variables for select element templates.
  796. *
  797. * Default template: select.html.twig.
  798. *
  799. * It is possible to group options together; to do this, change the format of
  800. * $options to an associative array in which the keys are group labels, and the
  801. * values are associative arrays in the normal $options format.
  802. *
  803. * @param $variables
  804. * An associative array containing:
  805. * - element: An associative array containing the properties of the element.
  806. * Properties used: #title, #value, #options, #description, #extra,
  807. * #multiple, #required, #name, #attributes, #size.
  808. */
  809. function template_preprocess_select(&$variables) {
  810. $element = $variables['element'];
  811. Element::setAttributes($element, array('id', 'name', 'size'));
  812. _form_set_attributes($element, array('form-select'));
  813. $variables['attributes'] = $element['#attributes'];
  814. $variables['options'] = form_select_options($element);
  815. }
  816. /**
  817. * Converts a select form element's options array into HTML.
  818. *
  819. * @param $element
  820. * An associative array containing the properties of the element.
  821. * @param $choices
  822. * Mixed: Either an associative array of items to list as choices, or an
  823. * object with an 'option' member that is an associative array. This
  824. * parameter is only used internally and should not be passed.
  825. *
  826. * @return
  827. * An HTML string of options for the select form element.
  828. */
  829. function form_select_options($element, $choices = NULL) {
  830. if (!isset($choices)) {
  831. if (empty($element['#options'])) {
  832. return '';
  833. }
  834. $choices = $element['#options'];
  835. }
  836. // array_key_exists() accommodates the rare event where $element['#value'] is NULL.
  837. // isset() fails in this situation.
  838. $value_valid = isset($element['#value']) || array_key_exists('#value', $element);
  839. $value_is_array = $value_valid && is_array($element['#value']);
  840. // Check if the element is multiple select and no value has been selected.
  841. $empty_value = (empty($element['#value']) && !empty($element['#multiple']));
  842. $options = '';
  843. foreach ($choices as $key => $choice) {
  844. if (is_array($choice)) {
  845. $options .= '<optgroup label="' . String::checkPlain($key) . '">';
  846. $options .= form_select_options($element, $choice);
  847. $options .= '</optgroup>';
  848. }
  849. elseif (is_object($choice) && isset($choice->option)) {
  850. $options .= form_select_options($element, $choice->option);
  851. }
  852. else {
  853. $key = (string) $key;
  854. $empty_choice = $empty_value && $key == '_none';
  855. if ($value_valid && ((!$value_is_array && (string) $element['#value'] === $key || ($value_is_array && in_array($key, $element['#value']))) || $empty_choice)) {
  856. $selected = ' selected="selected"';
  857. }
  858. else {
  859. $selected = '';
  860. }
  861. $options .= '<option value="' . String::checkPlain($key) . '"' . $selected . '>' . String::checkPlain($choice) . '</option>';
  862. }
  863. }
  864. return SafeMarkup::set($options);
  865. }
  866. /**
  867. * Returns the indexes of a select element's options matching a given key.
  868. *
  869. * This function is useful if you need to modify the options that are
  870. * already in a form element; for example, to remove choices which are
  871. * not valid because of additional filters imposed by another module.
  872. * One example might be altering the choices in a taxonomy selector.
  873. * To correctly handle the case of a multiple hierarchy taxonomy,
  874. * #options arrays can now hold an array of objects, instead of a
  875. * direct mapping of keys to labels, so that multiple choices in the
  876. * selector can have the same key (and label). This makes it difficult
  877. * to manipulate directly, which is why this helper function exists.
  878. *
  879. * This function does not support optgroups (when the elements of the
  880. * #options array are themselves arrays), and will return FALSE if
  881. * arrays are found. The caller must either flatten/restore or
  882. * manually do their manipulations in this case, since returning the
  883. * index is not sufficient, and supporting this would make the
  884. * "helper" too complicated and cumbersome to be of any help.
  885. *
  886. * As usual with functions that can return array() or FALSE, do not
  887. * forget to use === and !== if needed.
  888. *
  889. * @param $element
  890. * The select element to search.
  891. * @param $key
  892. * The key to look for.
  893. *
  894. * @return
  895. * An array of indexes that match the given $key. Array will be
  896. * empty if no elements were found. FALSE if optgroups were found.
  897. */
  898. function form_get_options($element, $key) {
  899. $keys = array();
  900. foreach ($element['#options'] as $index => $choice) {
  901. if (is_array($choice)) {
  902. return FALSE;
  903. }
  904. elseif (is_object($choice)) {
  905. if (isset($choice->option[$key])) {
  906. $keys[] = $index;
  907. }
  908. }
  909. elseif ($index == $key) {
  910. $keys[] = $index;
  911. }
  912. }
  913. return $keys;
  914. }
  915. /**
  916. * Prepares variables for fieldset element templates.
  917. *
  918. * Default template: fieldset.html.twig.
  919. *
  920. * @param array $variables
  921. * An associative array containing:
  922. * - element: An associative array containing the properties of the element.
  923. * Properties used: #attributes, #children, #description, #id, #title,
  924. * #value.
  925. */
  926. function template_preprocess_fieldset(&$variables) {
  927. $element = $variables['element'];
  928. Element::setAttributes($element, array('id'));
  929. _form_set_attributes($element, array('form-wrapper'));
  930. $variables['attributes'] = $element['#attributes'];
  931. $variables['attributes']['class'][] = 'form-item';
  932. $variables['prefix'] = isset($element['#field_prefix']) ? $element['#field_prefix'] : NULL;
  933. $variables['suffix'] = isset($element['#field_suffix']) ? $element['#field_suffix'] : NULL;
  934. $variables['children'] = $element['#children'];
  935. $legend_attributes = array();
  936. if (isset($element['#title_display']) && $element['#title_display'] == 'invisible') {
  937. $legend_attributes['class'][] = 'visually-hidden';
  938. }
  939. $variables['legend']['attributes'] = new Attribute($legend_attributes);
  940. $variables['legend']['title'] = (isset($element['#title']) && $element['#title'] !== '') ? Xss::filterAdmin($element['#title']) : '';
  941. $legend_span_attributes = array('class' => array('fieldset-legend'));
  942. if (!empty($element['#required'])) {
  943. $legend_span_attributes['class'][] = 'form-required';
  944. $variables['legend_span']['attributes'] = new Attribute($legend_span_attributes);
  945. }
  946. if (!empty($element['#description'])) {
  947. $description_id = $element['#attributes']['id'] . '--description';
  948. $description_attributes = array(
  949. 'class' => 'description',
  950. 'id' => $description_id,
  951. );
  952. $variables['description']['attributes'] = new Attribute($description_attributes);
  953. $variables['description']['content'] = $element['#description'];
  954. // Add the description's id to the fieldset aria attributes.
  955. $variables['attributes']['aria-describedby'] = $description_id;
  956. }
  957. }
  958. /**
  959. * Prepares variables for details element templates.
  960. *
  961. * Default template: details.html.twig.
  962. *
  963. * @param array $variables
  964. * An associative array containing:
  965. * - element: An associative array containing the properties of the element.
  966. * Properties used: #attributes, #children, #open,
  967. * #description, #id, #title, #value, #optional.
  968. */
  969. function template_preprocess_details(&$variables) {
  970. $element = $variables['element'];
  971. $variables['attributes'] = $element['#attributes'];
  972. $variables['summary_attributes'] = new Attribute();
  973. if (!empty($element['#title'])) {
  974. $variables['summary_attributes']['role'] = 'button';
  975. if (!empty($element['#attributes']['id'])) {
  976. $variables['summary_attributes']['aria-controls'] = $element['#attributes']['id'];
  977. }
  978. $variables['summary_attributes']['aria-expanded'] = !empty($element['#attributes']['open']);
  979. $variables['summary_attributes']['aria-pressed'] = $variables['summary_attributes']['aria-expanded'];
  980. }
  981. $variables['title'] = (!empty($element['#title'])) ? $element['#title'] : '';
  982. $variables['description'] = (!empty($element['#description'])) ? $element['#description'] : '';
  983. $variables['children'] = (isset($element['#children'])) ? $element['#children'] : '';
  984. $variables['value'] = (isset($element['#value'])) ? $element['#value'] : '';
  985. }
  986. /**
  987. * Prepares a #type 'radio' render element for theme_input().
  988. *
  989. * @param array $element
  990. * An associative array containing the properties of the element.
  991. * Properties used: #required, #return_value, #value, #attributes, #title,
  992. * #description.
  993. *
  994. * Note: The input "name" attribute needs to be sanitized before output, which
  995. * is currently done by initializing Drupal\Core\Template\Attribute with
  996. * all the attributes.
  997. *
  998. * @return array
  999. * The $element with prepared variables ready for theme_input().
  1000. */
  1001. function form_pre_render_radio($element) {
  1002. $element['#attributes']['type'] = 'radio';
  1003. Element::setAttributes($element, array('id', 'name', '#return_value' => 'value'));
  1004. if (isset($element['#return_value']) && $element['#value'] !== FALSE && $element['#value'] == $element['#return_value']) {
  1005. $element['#attributes']['checked'] = 'checked';
  1006. }
  1007. _form_set_attributes($element, array('form-radio'));
  1008. return $element;
  1009. }
  1010. /**
  1011. * Prepares variables for radios templates.
  1012. *
  1013. * Default template: radios.html.twig.
  1014. *
  1015. * @param array $variables
  1016. * An associative array containing:
  1017. * - element: An associative array containing the properties of the element.
  1018. * Properties used: #title, #value, #options, #description, #required,
  1019. * #attributes, #children.
  1020. */
  1021. function template_preprocess_radios(&$variables) {
  1022. $element = $variables['element'];
  1023. $variables['attributes'] = array();
  1024. if (isset($element['#id'])) {
  1025. $variables['attributes']['id'] = $element['#id'];
  1026. }
  1027. $variables['attributes']['class'][] = 'form-radios';
  1028. if (!empty($element['#attributes']['class'])) {
  1029. $variables['attributes']['class'] = array_merge($variables['attributes']['class'], $element['#attributes']['class']);
  1030. }
  1031. if (isset($element['#attributes']['title'])) {
  1032. $variables['attributes']['title'] = $element['#attributes']['title'];
  1033. }
  1034. $variables['children'] = $element['#children'];
  1035. }
  1036. /**
  1037. * Expand a password_confirm field into two text boxes.
  1038. */
  1039. function form_process_password_confirm($element) {
  1040. $element['pass1'] = array(
  1041. '#type' => 'password',
  1042. '#title' => t('Password'),
  1043. '#value' => empty($element['#value']) ? NULL : $element['#value']['pass1'],
  1044. '#required' => $element['#required'],
  1045. '#attributes' => array('class' => array('password-field')),
  1046. );
  1047. $element['pass2'] = array(
  1048. '#type' => 'password',
  1049. '#title' => t('Confirm password'),
  1050. '#value' => empty($element['#value']) ? NULL : $element['#value']['pass2'],
  1051. '#required' => $element['#required'],
  1052. '#attributes' => array('class' => array('password-confirm')),
  1053. );
  1054. $element['#element_validate'] = array('password_confirm_validate');
  1055. $element['#tree'] = TRUE;
  1056. if (isset($element['#size'])) {
  1057. $element['pass1']['#size'] = $element['pass2']['#size'] = $element['#size'];
  1058. }
  1059. return $element;
  1060. }
  1061. /**
  1062. * Validates a password_confirm element.
  1063. */
  1064. function password_confirm_validate($element, &$element_state) {
  1065. $pass1 = trim($element['pass1']['#value']);
  1066. $pass2 = trim($element['pass2']['#value']);
  1067. if (!empty($pass1) || !empty($pass2)) {
  1068. if (strcmp($pass1, $pass2)) {
  1069. form_error($element, $element_state, t('The specified passwords do not match.'));
  1070. }
  1071. }
  1072. elseif ($element['#required'] && !empty($element_state['input'])) {
  1073. form_error($element, $element_state, t('Password field is required.'));
  1074. }
  1075. // Password field must be converted from a two-element array into a single
  1076. // string regardless of validation results.
  1077. form_set_value($element['pass1'], NULL, $element_state);
  1078. form_set_value($element['pass2'], NULL, $element_state);
  1079. form_set_value($element, $pass1, $element_state);
  1080. return $element;
  1081. }
  1082. /**
  1083. * Adds form-specific attributes to a 'date' #type element.
  1084. *
  1085. * Supports HTML5 types of 'date', 'datetime', 'datetime-local', and 'time'.
  1086. * Falls back to a plain textfield. Used as a sub-element by the datetime
  1087. * element type.
  1088. *
  1089. * @param array $element
  1090. * An associative array containing the properties of the element.
  1091. * Properties used: #title, #value, #options, #description, #required,
  1092. * #attributes, #id, #name, #type, #min, #max, #step, #value, #size.
  1093. *
  1094. * Note: The input "name" attribute needs to be sanitized before output, which
  1095. * is currently done by initializing Drupal\Core\Template\Attribute with
  1096. * all the attributes.
  1097. *
  1098. * @return array
  1099. * The $element with prepared variables ready for #theme 'input__date'.
  1100. */
  1101. function form_pre_render_date($element) {
  1102. if (empty($element['#attributes']['type'])) {
  1103. $element['#attributes']['type'] = 'date';
  1104. }
  1105. Element::setAttributes($element, array('id', 'name', 'type', 'min', 'max', 'step', 'value', 'size'));
  1106. _form_set_attributes($element, array('form-' . $element['#attributes']['type']));
  1107. return $element;
  1108. }
  1109. /**
  1110. * Sets the value for a weight element, with zero as a default.
  1111. */
  1112. function weight_value(&$form) {
  1113. if (isset($form['#default_value'])) {
  1114. $form['#value'] = $form['#default_value'];
  1115. }
  1116. else {
  1117. $form['#value'] = 0;
  1118. }
  1119. }
  1120. /**
  1121. * Expands a radios element into individual radio elements.
  1122. */
  1123. function form_process_radios($element) {
  1124. if (count($element['#options']) > 0) {
  1125. $weight = 0;
  1126. foreach ($element['#options'] as $key => $choice) {
  1127. // Maintain order of options as defined in #options, in case the element
  1128. // defines custom option sub-elements, but does not define all option
  1129. // sub-elements.
  1130. $weight += 0.001;
  1131. $element += array($key => array());
  1132. // Generate the parents as the autogenerator does, so we will have a
  1133. // unique id for each radio button.
  1134. $parents_for_id = array_merge($element['#parents'], array($key));
  1135. $element[$key] += array(
  1136. '#type' => 'radio',
  1137. '#title' => $choice,
  1138. // The key is sanitized in Drupal\Core\Template\Attribute during output
  1139. // from the theme function.
  1140. '#return_value' => $key,
  1141. // Use default or FALSE. A value of FALSE means that the radio button is
  1142. // not 'checked'.
  1143. '#default_value' => isset($element['#default_value']) ? $element['#default_value'] : FALSE,
  1144. '#attributes' => $element['#attributes'],
  1145. '#parents' => $element['#parents'],
  1146. '#id' => drupal_html_id('edit-' . implode('-', $parents_for_id)),
  1147. '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,
  1148. '#weight' => $weight,
  1149. );
  1150. }
  1151. }
  1152. return $element;
  1153. }
  1154. /**
  1155. * Prepares a #type 'checkbox' render element for theme_input().
  1156. *
  1157. * @param array $element
  1158. * An associative array containing the properties of the element.
  1159. * Properties used: #title, #value, #return_value, #description, #required,
  1160. * #attributes, #checked.
  1161. *
  1162. * @return array
  1163. * The $element with prepared variables ready for theme_input().
  1164. */
  1165. function form_pre_render_checkbox($element) {
  1166. $element['#attributes']['type'] = 'checkbox';
  1167. Element::setAttributes($element, array('id', 'name', '#return_value' => 'value'));
  1168. // Unchecked checkbox has #value of integer 0.
  1169. if (!empty($element['#checked'])) {
  1170. $element['#attributes']['checked'] = 'checked';
  1171. }
  1172. _form_set_attributes($element, array('form-checkbox'));
  1173. return $element;
  1174. }
  1175. /**
  1176. * Prepares variables for checkboxes templates.
  1177. *
  1178. * Default template: checkboxes.html.twig.
  1179. *
  1180. * @param array $variables
  1181. * An associative array containing:
  1182. * - element: An associative array containing the properties of the element.
  1183. * Properties used: #children, #attributes.
  1184. */
  1185. function template_preprocess_checkboxes(&$variables) {
  1186. $element = $variables['element'];
  1187. $variables['attributes'] = array();
  1188. if (isset($element['#id'])) {
  1189. $variables['attributes']['id'] = $element['#id'];
  1190. }
  1191. $variables['attributes']['class'] = array();
  1192. $variables['attributes']['class'][] = 'form-checkboxes';
  1193. if (!empty($element['#attributes']['class'])) {
  1194. $variables['attributes']['class'] = array_merge($variables['attributes']['class'], $element['#attributes']['class']);
  1195. }
  1196. if (isset($element['#attributes']['title'])) {
  1197. $variables['attributes']['title'] = $element['#attributes']['title'];
  1198. }
  1199. $variables['children'] = $element['#children'];
  1200. }
  1201. /**
  1202. * Adds form element theming to an element if its title or description is set.
  1203. *
  1204. * This is used as a pre render function for checkboxes and radios.
  1205. */
  1206. function form_pre_render_conditional_form_element($element) {
  1207. // Set the element's title attribute to show #title as a tooltip, if needed.
  1208. if (isset($element['#title']) && $element['#title_display'] == 'attribute') {
  1209. $element['#attributes']['title'] = $element['#title'];
  1210. if (!empty($element['#required'])) {
  1211. // Append an indication that this field is required.
  1212. $element['#attributes']['title'] .= ' (' . t('Required') . ')';
  1213. }
  1214. }
  1215. if (isset($element['#title']) || isset($element['#description'])) {
  1216. // @see #type 'fieldgroup'
  1217. $element['#theme_wrappers'][] = 'fieldset';
  1218. $element['#attributes']['class'][] = 'fieldgroup';
  1219. $element['#attributes']['class'][] = 'form-composite';
  1220. }
  1221. return $element;
  1222. }
  1223. /**
  1224. * Processes a form button element.
  1225. */
  1226. function form_process_button($element, $form_state) {
  1227. // If this is a button intentionally allowing incomplete form submission
  1228. // (e.g., a "Previous" or "Add another item" button), then also skip
  1229. // client-side validation.
  1230. if (isset($element['#limit_validation_errors']) && $element['#limit_validation_errors'] !== FALSE) {
  1231. $element['#attributes']['formnovalidate'] = 'formnovalidate';
  1232. }
  1233. return $element;
  1234. }
  1235. /**
  1236. * Sets the #checked property of a checkbox element.
  1237. */
  1238. function form_process_checkbox($element, $form_state) {
  1239. $value = $element['#value'];
  1240. $return_value = $element['#return_value'];
  1241. // On form submission, the #value of an available and enabled checked
  1242. // checkbox is #return_value, and the #value of an available and enabled
  1243. // unchecked checkbox is integer 0. On not submitted forms, and for
  1244. // checkboxes with #access=FALSE or #disabled=TRUE, the #value is
  1245. // #default_value (integer 0 if #default_value is NULL). Most of the time,
  1246. // a string comparison of #value and #return_value is sufficient for
  1247. // determining the "checked" state, but a value of TRUE always means checked
  1248. // (even if #return_value is 'foo'), and a value of FALSE or integer 0 always
  1249. // means unchecked (even if #return_value is '' or '0').
  1250. if ($value === TRUE || $value === FALSE || $value === 0) {
  1251. $element['#checked'] = (bool) $value;
  1252. }
  1253. else {
  1254. // Compare as strings, so that 15 is not considered equal to '15foo', but 1
  1255. // is considered equal to '1'. This cast does not imply that either #value
  1256. // or #return_value is expected to be a string.
  1257. $element['#checked'] = ((string) $value === (string) $return_value);
  1258. }
  1259. return $element;
  1260. }
  1261. /**
  1262. * Processes a checkboxes form element.
  1263. */
  1264. function form_process_checkboxes($element) {
  1265. $value = is_array($element['#value']) ? $element['#value'] : array();
  1266. $element['#tree'] = TRUE;
  1267. if (count($element['#options']) > 0) {
  1268. if (!isset($element['#default_value']) || $element['#default_value'] == 0) {
  1269. $element['#default_value'] = array();
  1270. }
  1271. $weight = 0;
  1272. foreach ($element['#options'] as $key => $choice) {
  1273. // Integer 0 is not a valid #return_value, so use '0' instead.
  1274. // @see form_type_checkbox_value().
  1275. // @todo For Drupal 8, cast all integer keys to strings for consistency
  1276. // with form_process_radios().
  1277. if ($key === 0) {
  1278. $key = '0';
  1279. }
  1280. // Maintain order of options as defined in #options, in case the element
  1281. // defines custom option sub-elements, but does not define all option
  1282. // sub-elements.
  1283. $weight += 0.001;
  1284. $element += array($key => array());
  1285. $element[$key] += array(
  1286. '#type' => 'checkbox',
  1287. '#title' => $choice,
  1288. '#return_value' => $key,
  1289. '#default_value' => isset($value[$key]) ? $key : NULL,
  1290. '#attributes' => $element['#attributes'],
  1291. '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,
  1292. '#weight' => $weight,
  1293. );
  1294. }
  1295. }
  1296. return $element;
  1297. }
  1298. /**
  1299. * Processes a form actions container element.
  1300. *
  1301. * @param $element
  1302. * An associative array containing the properties and children of the
  1303. * form actions container.
  1304. * @param $form_state
  1305. * The $form_state array for the form this element belongs to.
  1306. *
  1307. * @return
  1308. * The processed element.
  1309. */
  1310. function form_process_actions($element, &$form_state) {
  1311. $element['#attributes']['class'][] = 'form-actions';
  1312. return $element;
  1313. }
  1314. /**
  1315. * #pre_render callback for #type 'actions'.
  1316. *
  1317. * This callback iterates over all child elements of the #type 'actions'
  1318. * container to look for elements with a #dropbutton property, so as to group
  1319. * those elements into dropbuttons. As such, it works similar to #group, but is
  1320. * specialized for dropbuttons.
  1321. *
  1322. * The value of #dropbutton denotes the dropbutton to group the child element
  1323. * into. For example, two different values of 'foo' and 'bar' on child elements
  1324. * would generate two separate dropbuttons, which each contain the corresponding
  1325. * buttons.
  1326. *
  1327. * @param array $element
  1328. * The #type 'actions' element to process.
  1329. *
  1330. * @return array
  1331. * The processed #type 'actions' element, including individual buttons grouped
  1332. * into new #type 'dropbutton' elements.
  1333. */
  1334. function form_pre_render_actions_dropbutton(array $element) {
  1335. $dropbuttons = array();
  1336. foreach (Element::children($element, TRUE) as $key) {
  1337. if (isset($element[$key]['#dropbutton'])) {
  1338. $dropbutton = $element[$key]['#dropbutton'];
  1339. // If there is no dropbutton for this button group yet, create one.
  1340. if (!isset($dropbuttons[$dropbutton])) {
  1341. $dropbuttons[$dropbutton] = array(
  1342. '#type' => 'dropbutton',
  1343. );
  1344. }
  1345. // Add this button to the corresponding dropbutton.
  1346. // @todo Change #type 'dropbutton' to be based on theme_item_list()
  1347. // instead of links.html.twig to avoid this preemptive rendering.
  1348. $button = drupal_render($element[$key]);
  1349. $dropbuttons[$dropbutton]['#links'][$key] = array(
  1350. 'title' => $button,
  1351. 'html' => TRUE,
  1352. );
  1353. }
  1354. }
  1355. // @todo For now, all dropbuttons appear first. Consider to invent a more
  1356. // fancy sorting/injection algorithm here.
  1357. return $dropbuttons + $element;
  1358. }
  1359. /**
  1360. * #process callback for #pattern form element property.
  1361. *
  1362. * @param $element
  1363. * An associative array containing the properties and children of the
  1364. * generic input element.
  1365. * @param $form_state
  1366. * The $form_state array for the form this element belongs to.
  1367. *
  1368. * @return
  1369. * The processed element.
  1370. *
  1371. * @see form_validate_pattern()
  1372. */
  1373. function form_process_pattern($element, &$form_state) {
  1374. if (isset($element['#pattern']) && !isset($element['#attributes']['pattern'])) {
  1375. $element['#attributes']['pattern'] = $element['#pattern'];
  1376. $element['#element_validate'][] = 'form_validate_pattern';
  1377. }
  1378. return $element;
  1379. }
  1380. /**
  1381. * #element_validate callback for #pattern form element property.
  1382. *
  1383. * @param $element
  1384. * An associative array containing the properties and children of the
  1385. * generic form element.
  1386. * @param $form_state
  1387. * The $form_state array for the form this element belongs to.
  1388. *
  1389. * @see form_process_pattern()
  1390. */
  1391. function form_validate_pattern($element, &$form_state) {
  1392. if ($element['#value'] !== '') {
  1393. // The pattern must match the entire string and should have the same
  1394. // behavior as the RegExp object in ECMA 262.
  1395. // - Use bracket-style delimiters to avoid introducing a special delimiter
  1396. // character like '/' that would have to be escaped.
  1397. // - Put in brackets so that the pattern can't interfere with what's
  1398. // prepended and appended.
  1399. $pattern = '{^(?:' . $element['#pattern'] . ')$}';
  1400. if (!preg_match($pattern, $element['#value'])) {
  1401. form_error($element, $form_state, t('%name field is not in the right format.', array('%name' => $element['#title'])));
  1402. }
  1403. }
  1404. }
  1405. /**
  1406. * Processes a container element.
  1407. *
  1408. * @param $element
  1409. * An associative array containing the properties and children of the
  1410. * container.
  1411. * @param $form_state
  1412. * The $form_state array for the form this element belongs to.
  1413. *
  1414. * @return
  1415. * The processed element.
  1416. */
  1417. function form_process_container($element, &$form_state) {
  1418. // Generate the ID of the element if it's not explicitly given.
  1419. if (!isset($element['#id'])) {
  1420. $element['#id'] = drupal_html_id(implode('-', $element['#parents']) . '-wrapper');
  1421. }
  1422. return $element;
  1423. }
  1424. /**
  1425. * Returns HTML for a table with radio buttons or checkboxes.
  1426. *
  1427. * @param $variables
  1428. * An associative array containing:
  1429. * - element: An associative array containing the properties and children of
  1430. * the tableselect element. Properties used: #header, #options, #empty,
  1431. * and #js_select. The #options property is an array of selection options;
  1432. * each array element of #options is an array of properties. These
  1433. * properties can include #attributes, which is added to the
  1434. * table row's HTML attributes; see theme_table(). An example of per-row
  1435. * options:
  1436. * @code
  1437. * $options = array(
  1438. * array(
  1439. * 'title' => 'How to Learn Drupal',
  1440. * 'content_type' => 'Article',
  1441. * 'status' => 'published',
  1442. * '#attributes' => array('class' => array('article-row')),
  1443. * ),
  1444. * array(
  1445. * 'title' => 'Privacy Policy',
  1446. * 'content_type' => 'Page',
  1447. * 'status' => 'published',
  1448. * '#attributes' => array('class' => array('page-row')),
  1449. * ),
  1450. * );
  1451. * $header = array(
  1452. * 'title' => t('Title'),
  1453. * 'content_type' => t('Content type'),
  1454. * 'status' => t('Status'),
  1455. * );
  1456. * $form['table'] = array(
  1457. * '#type' => 'tableselect',
  1458. * '#header' => $header,
  1459. * '#options' => $options,
  1460. * '#empty' => t('No content available.'),
  1461. * );
  1462. * @endcode
  1463. *
  1464. * @ingroup themeable
  1465. */
  1466. function theme_tableselect($variables) {
  1467. $element = $variables['element'];
  1468. $table = array(
  1469. '#type' => 'table',
  1470. );
  1471. $rows = array();
  1472. $header = $element['#header'];
  1473. if (!empty($element['#options'])) {
  1474. // Generate a table row for each selectable item in #options.
  1475. foreach (Element::children($element) as $key) {
  1476. $row = array();
  1477. $row['data'] = array();
  1478. if (isset($element['#options'][$key]['#attributes'])) {
  1479. $row += $element['#options'][$key]['#attributes'];
  1480. }
  1481. // Render the checkbox / radio element.
  1482. $row['data'][] = drupal_render($element[$key]);
  1483. // As theme_table only maps header and row columns by order, create the
  1484. // correct order by iterating over the header fields.
  1485. foreach ($element['#header'] as $fieldname => $title) {
  1486. // A row cell can span over multiple headers, which means less row cells
  1487. // than headers could be present.
  1488. if (isset($element['#options'][$key][$fieldname])) {
  1489. // A header can span over multiple cells and in this case the cells
  1490. // are passed in an array. The order of this array determines the
  1491. // order in which they are added.
  1492. if (is_array($element['#options'][$key][$fieldname]) && !isset($element['#options'][$key][$fieldname]['data'])) {
  1493. foreach ($element['#options'][$key][$fieldname] as $cell) {
  1494. $row['data'][] = $cell;
  1495. }
  1496. }
  1497. else {
  1498. $row['data'][] = $element['#options'][$key][$fieldname];
  1499. }
  1500. }
  1501. }
  1502. $rows[] = $row;
  1503. }
  1504. // Add an empty header or a "Select all" checkbox to provide room for the
  1505. // checkboxes/radios in the first table column.
  1506. if ($element['#js_select']) {
  1507. // Add a "Select all" checkbox.
  1508. $table['#attached']['library'][] = 'core/drupal.tableselect';
  1509. array_unshift($header, array('class' => array('select-all')));
  1510. }
  1511. else {
  1512. // Add an empty header when radio buttons are displayed or a "Select all"
  1513. // checkbox is not desired.
  1514. array_unshift($header, '');
  1515. }
  1516. }
  1517. $table += array(
  1518. '#header' => $header,
  1519. '#rows' => $rows,
  1520. '#empty' => $element['#empty'],
  1521. '#attributes' => $element['#attributes'],
  1522. );
  1523. return drupal_render($table);
  1524. }
  1525. /**
  1526. * Creates checkbox or radio elements to populate a tableselect table.
  1527. *
  1528. * @param $element
  1529. * An associative array containing the properties and children of the
  1530. * tableselect element.
  1531. *
  1532. * @return
  1533. * The processed element.
  1534. */
  1535. function form_process_tableselect($element) {
  1536. if ($element['#multiple']) {
  1537. $value = is_array($element['#value']) ? $element['#value'] : array();
  1538. }
  1539. else {
  1540. // Advanced selection behavior makes no sense for radios.
  1541. $element['#js_select'] = FALSE;
  1542. }
  1543. $element['#tree'] = TRUE;
  1544. if (count($element['#options']) > 0) {
  1545. if (!isset($element['#default_value']) || $element['#default_value'] === 0) {
  1546. $element['#default_value'] = array();
  1547. }
  1548. // Create a checkbox or radio for each item in #options in such a way that
  1549. // the value of the tableselect element behaves as if it had been of type
  1550. // checkboxes or radios.
  1551. foreach ($element['#options'] as $key => $choice) {
  1552. // Do not overwrite manually created children.
  1553. if (!isset($element[$key])) {
  1554. if ($element['#multiple']) {
  1555. $title = '';
  1556. if (!empty($element['#options'][$key]['title']['data']['#title'])) {
  1557. $title = t('Update @title', array(
  1558. '@title' => $element['#options'][$key]['title']['data']['#title'],
  1559. ));
  1560. }
  1561. $element[$key] = array(
  1562. '#type' => 'checkbox',
  1563. '#title' => $title,
  1564. '#title_display' => 'invisible',
  1565. '#return_value' => $key,
  1566. '#default_value' => isset($value[$key]) ? $key : NULL,
  1567. '#attributes' => $element['#attributes'],
  1568. );
  1569. }
  1570. else {
  1571. // Generate the parents as the autogenerator does, so we will have a
  1572. // unique id for each radio button.
  1573. $parents_for_id = array_merge($element['#parents'], array($key));
  1574. $element[$key] = array(
  1575. '#type' => 'radio',
  1576. '#title' => '',
  1577. '#return_value' => $key,
  1578. '#default_value' => ($element['#default_value'] == $key) ? $key : NULL,
  1579. '#attributes' => $element['#attributes'],
  1580. '#parents' => $element['#parents'],
  1581. '#id' => drupal_html_id('edit-' . implode('-', $parents_for_id)),
  1582. '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,
  1583. );
  1584. }
  1585. if (isset($element['#options'][$key]['#weight'])) {
  1586. $element[$key]['#weight'] = $element['#options'][$key]['#weight'];
  1587. }
  1588. }
  1589. }
  1590. }
  1591. else {
  1592. $element['#value'] = array();
  1593. }
  1594. return $element;
  1595. }
  1596. /**
  1597. * #process callback for #type 'table' to add tableselect support.
  1598. *
  1599. * @param array $element
  1600. * An associative array containing the properties and children of the
  1601. * table element.
  1602. * @param array $form_state
  1603. * The current state of the form.
  1604. *
  1605. * @return array
  1606. * The processed element.
  1607. *
  1608. * @see form_process_tableselect()
  1609. * @see theme_tableselect()
  1610. */
  1611. function form_process_table($element, &$form_state) {
  1612. if ($element['#tableselect']) {
  1613. if ($element['#multiple']) {
  1614. $value = is_array($element['#value']) ? $element['#value'] : array();
  1615. }
  1616. // Advanced selection behavior makes no sense for radios.
  1617. else {
  1618. $element['#js_select'] = FALSE;
  1619. }
  1620. // Add a "Select all" checkbox column to the header.
  1621. // @todo D8: Rename into #select_all?
  1622. if ($element['#js_select']) {
  1623. $element['#attached']['library'][] = 'core/drupal.tableselect';
  1624. array_unshift($element['#header'], array('class' => array('select-all')));
  1625. }
  1626. // Add an empty header column for radio buttons or when a "Select all"
  1627. // checkbox is not desired.
  1628. else {
  1629. array_unshift($element['#header'], '');
  1630. }
  1631. if (!isset($element['#default_value']) || $element['#default_value'] === 0) {
  1632. $element['#default_value'] = array();
  1633. }
  1634. // Create a checkbox or radio for each row in a way that the value of the
  1635. // tableselect element behaves as if it had been of #type checkboxes or
  1636. // radios.
  1637. foreach (Element::children($element) as $key) {
  1638. $row = &$element[$key];
  1639. // Prepare the element #parents for the tableselect form element.
  1640. // Their values have to be located in child keys (#tree is ignored), since
  1641. // form_validate_table() has to be able to validate whether input (for the
  1642. // parent #type 'table' element) has been submitted.
  1643. $element_parents = array_merge($element['#parents'], array($key));
  1644. // Since the #parents of the tableselect form element will equal the
  1645. // #parents of the row element, prevent FormBuilder from auto-generating
  1646. // an #id for the row element, since drupal_html_id() would automatically
  1647. // append a suffix to the tableselect form element's #id otherwise.
  1648. $row['#id'] = drupal_html_id('edit-' . implode('-', $element_parents) . '-row');
  1649. // Do not overwrite manually created children.
  1650. if (!isset($row['select'])) {
  1651. // Determine option label; either an assumed 'title' column, or the
  1652. // first available column containing a #title or #markup.
  1653. // @todo Consider to add an optional $element[$key]['#title_key']
  1654. // defaulting to 'title'?
  1655. unset($label_element);
  1656. $title = NULL;
  1657. if (isset($row['title']['#type']) && $row['title']['#type'] == 'label') {
  1658. $label_element = &$row['title'];
  1659. }
  1660. else {
  1661. if (!empty($row['title']['#title'])) {
  1662. $title = $row['title']['#title'];
  1663. }
  1664. else {
  1665. foreach (Element::children($row) as $column) {
  1666. if (isset($row[$column]['#title'])) {
  1667. $title = $row[$column]['#title'];
  1668. break;
  1669. }
  1670. if (isset($row[$column]['#markup'])) {
  1671. $title = $row[$column]['#markup'];
  1672. break;
  1673. }
  1674. }
  1675. }
  1676. if (isset($title) && $title !== '') {
  1677. $title = t('Update !title', array('!title' => $title));
  1678. }
  1679. }
  1680. // Prepend the select column to existing columns.
  1681. $row = array('select' => array()) + $row;
  1682. $row['select'] += array(
  1683. '#type' => $element['#multiple'] ? 'checkbox' : 'radio',
  1684. '#id' => drupal_html_id('edit-' . implode('-', $element_parents)),
  1685. // @todo If rows happen to use numeric indexes instead of string keys,
  1686. // this results in a first row with $key === 0, which is always FALSE.
  1687. '#return_value' => $key,
  1688. '#attributes' => $element['#attributes'],
  1689. '#wrapper_attributes' => array(
  1690. 'class' => array('table-select'),
  1691. ),
  1692. );
  1693. if ($element['#multiple']) {
  1694. $row['select']['#default_value'] = isset($value[$key]) ? $key : NULL;
  1695. $row['select']['#parents'] = $element_parents;
  1696. }
  1697. else {
  1698. $row['select']['#default_value'] = ($element['#default_value'] == $key ? $key : NULL);
  1699. $row['select']['#parents'] = $element['#parents'];
  1700. }
  1701. if (isset($label_element)) {
  1702. $label_element['#id'] = $row['select']['#id'] . '--label';
  1703. $label_element['#for'] = $row['select']['#id'];
  1704. $row['select']['#attributes']['aria-labelledby'] = $label_element['#id'];
  1705. $row['select']['#title_display'] = 'none';
  1706. }
  1707. else {
  1708. $row['select']['#title'] = $title;
  1709. $row['select']['#title_display'] = 'invisible';
  1710. }
  1711. }
  1712. }
  1713. }
  1714. return $element;
  1715. }
  1716. /**
  1717. * #element_validate callback for #type 'table'.
  1718. *
  1719. * @param array $element
  1720. * An associative array containing the properties and children of the
  1721. * table element.
  1722. * @param array $form_state
  1723. * The current state of the form.
  1724. */
  1725. function form_validate_table($element, &$form_state) {
  1726. // Skip this validation if the button to submit the form does not require
  1727. // selected table row data.
  1728. if (empty($form_state['triggering_element']['#tableselect'])) {
  1729. return;
  1730. }
  1731. if ($element['#multiple']) {
  1732. if (!is_array($element['#value']) || !count(array_filter($element['#value']))) {
  1733. form_error($element, $form_state, t('No items selected.'));
  1734. }
  1735. }
  1736. elseif (!isset($element['#value']) || $element['#value'] === '') {
  1737. form_error($element, $form_state, t('No item selected.'));
  1738. }
  1739. }
  1740. /**
  1741. * Processes a machine-readable name form element.
  1742. *
  1743. * @param $element
  1744. * The form element to process. Properties used:
  1745. * - #machine_name: An associative array containing:
  1746. * - exists: A callable to invoke for checking whether a submitted machine
  1747. * name value already exists. The submitted value is passed as an
  1748. * argument. In most cases, an existing API or menu argument loader
  1749. * function can be re-used. The callback is only invoked, if the submitted
  1750. * value differs from the element's #default_value.
  1751. * - source: (optional) The #array_parents of the form element containing
  1752. * the human-readable name (i.e., as contained in the $form structure) to
  1753. * use as source for the machine name. Defaults to array('label').
  1754. * - label: (optional) A text to display as label for the machine name value
  1755. * after the human-readable name form element. Defaults to "Machine name".
  1756. * - replace_pattern: (optional) A regular expression (without delimiters)
  1757. * matching disallowed characters in the machine name. Defaults to
  1758. * '[^a-z0-9_]+'.
  1759. * - replace: (optional) A character to replace disallowed characters in the
  1760. * machine name via JavaScript. Defaults to '_' (underscore). When using a
  1761. * different character, 'replace_pattern' needs to be set accordingly.
  1762. * - error: (optional) A custom form error message string to show, if the
  1763. * machine name contains disallowed characters.
  1764. * - standalone: (optional) Whether the live preview should stay in its own
  1765. * form element rather than in the suffix of the source element. Defaults
  1766. * to FALSE.
  1767. * - #maxlength: (optional) Should be set to the maximum allowed length of the
  1768. * machine name. Defaults to 64.
  1769. * - #disabled: (optional) Should be set to TRUE in case an existing machine
  1770. * name must not be changed after initial creation.
  1771. */
  1772. function form_process_machine_name($element, &$form_state) {
  1773. // We need to pass the langcode to the client.
  1774. $language = \Drupal::languageManager()->getCurrentLanguage();
  1775. // Apply default form element properties.
  1776. $element += array(
  1777. '#title' => t('Machine-readable name'),
  1778. '#description' => t('A unique machine-readable name. Can only contain lowercase letters, numbers, and underscores.'),
  1779. '#machine_name' => array(),
  1780. '#field_prefix' => '',
  1781. '#field_suffix' => '',
  1782. '#suffix' => '',
  1783. );
  1784. // A form element that only wants to set one #machine_name property (usually
  1785. // 'source' only) would leave all other properties undefined, if the defaults
  1786. // were defined in hook_element_info(). Therefore, we apply the defaults here.
  1787. $element['#machine_name'] += array(
  1788. 'source' => array('label'),
  1789. 'target' => '#' . $element['#id'],
  1790. 'label' => t('Machine name'),
  1791. 'replace_pattern' => '[^a-z0-9_]+',
  1792. 'replace' => '_',
  1793. 'standalone' => FALSE,
  1794. 'field_prefix' => $element['#field_prefix'],
  1795. 'field_suffix' => $element['#field_suffix'],
  1796. );
  1797. // By default, machine names are restricted to Latin alphanumeric characters.
  1798. // So, default to LTR directionality.
  1799. if (!isset($element['#attributes'])) {
  1800. $element['#attributes'] = array();
  1801. }
  1802. $element['#attributes'] += array('dir' => 'ltr');
  1803. // The source element defaults to array('name'), but may have been overidden.
  1804. if (empty($element['#machine_name']['source'])) {
  1805. return $element;
  1806. }
  1807. // Retrieve the form element containing the human-readable name from the
  1808. // complete form in $form_state. By reference, because we may need to append
  1809. // a #field_suffix that will hold the live preview.
  1810. $key_exists = NULL;
  1811. $source = NestedArray::getValue($form_state['complete_form'], $element['#machine_name']['source'], $key_exists);
  1812. if (!$key_exists) {
  1813. return $element;
  1814. }
  1815. $suffix_id = $source['#id'] . '-machine-name-suffix';
  1816. $element['#machine_name']['suffix'] = '#' . $suffix_id;
  1817. if ($element['#machine_name']['standalone']) {
  1818. $element['#suffix'] = SafeMarkup::set($element['#suffix'] . ' <small id="' . $suffix_id . '">&nbsp;</small>');
  1819. }
  1820. else {
  1821. // Append a field suffix to the source form element, which will contain
  1822. // the live preview of the machine name.
  1823. $source += array('#field_suffix' => '');
  1824. $source['#field_suffix'] = SafeMarkup::set($source['#field_suffix'] . ' <small id="' . $suffix_id . '">&nbsp;</small>');
  1825. $parents = array_merge($element['#machine_name']['source'], array('#field_suffix'));
  1826. NestedArray::setValue($form_state['complete_form'], $parents, $source['#field_suffix']);
  1827. }
  1828. $js_settings = array(
  1829. 'type' => 'setting',
  1830. 'data' => array(
  1831. 'machineName' => array(
  1832. '#' . $source['#id'] => $element['#machine_name'],
  1833. ),
  1834. 'langcode' => $language->id,
  1835. ),
  1836. );
  1837. $element['#attached']['library'][] = 'core/drupal.machine-name';
  1838. $element['#attached']['js'][] = $js_settings;
  1839. return $element;
  1840. }
  1841. /**
  1842. * Form element validation handler for machine_name elements.
  1843. *
  1844. * Note that #maxlength is validated by _form_validate() already.
  1845. */
  1846. function form_validate_machine_name(&$element, &$form_state) {
  1847. // Verify that the machine name not only consists of replacement tokens.
  1848. if (preg_match('@^' . $element['#machine_name']['replace'] . '+$@', $element['#value'])) {
  1849. form_error($element, $form_state, t('The machine-readable name must contain unique characters.'));
  1850. }
  1851. // Verify that the machine name contains no disallowed characters.
  1852. if (preg_match('@' . $element['#machine_name']['replace_pattern'] . '@', $element['#value'])) {
  1853. if (!isset($element['#machine_name']['error'])) {
  1854. // Since a hyphen is the most common alternative replacement character,
  1855. // a corresponding validation error message is supported here.
  1856. if ($element['#machine_name']['replace'] == '-') {
  1857. form_error($element, $form_state, t('The machine-readable name must contain only lowercase letters, numbers, and hyphens.'));
  1858. }
  1859. // Otherwise, we assume the default (underscore).
  1860. else {
  1861. form_error($element, $form_state, t('The machine-readable name must contain only lowercase letters, numbers, and underscores.'));
  1862. }
  1863. }
  1864. else {
  1865. form_error($element, $form_state, $element['#machine_name']['error']);
  1866. }
  1867. }
  1868. // Verify that the machine name is unique.
  1869. if ($element['#default_value'] !== $element['#value']) {
  1870. $function = $element['#machine_name']['exists'];
  1871. if (call_user_func($function, $element['#value'], $element, $form_state)) {
  1872. form_error($element, $form_state, t('The machine-readable name is already in use. It must be unique.'));
  1873. }
  1874. }
  1875. }
  1876. /**
  1877. * Arranges elements into groups.
  1878. *
  1879. * @param $element
  1880. * An associative array containing the properties and children of the
  1881. * element. Note that $element must be taken by reference here, so processed
  1882. * child elements are taken over into $form_state.
  1883. * @param $form_state
  1884. * The $form_state array for the form this element belongs to.
  1885. *
  1886. * @return
  1887. * The processed element.
  1888. */
  1889. function form_process_group(&$element, &$form_state) {
  1890. $parents = implode('][', $element['#parents']);
  1891. // Each details element forms a new group. The #type 'vertical_tabs' basically
  1892. // only injects a new details element.
  1893. $form_state['groups'][$parents]['#group_exists'] = TRUE;
  1894. $element['#groups'] = &$form_state['groups'];
  1895. // Process vertical tabs group member details elements.
  1896. if (isset($element['#group'])) {
  1897. // Add this details element to the defined group (by reference).
  1898. $group = $element['#group'];
  1899. $form_state['groups'][$group][] = &$element;
  1900. }
  1901. return $element;
  1902. }
  1903. /**
  1904. * Adds form element theming to details.
  1905. *
  1906. * @param $element
  1907. * An associative array containing the properties and children of the
  1908. * details.
  1909. *
  1910. * @return
  1911. * The modified element.
  1912. */
  1913. function form_pre_render_details($element) {
  1914. Element::setAttributes($element, array('id'));
  1915. // The .form-wrapper class is required for #states to treat details like
  1916. // containers.
  1917. _form_set_attributes($element, array('form-wrapper'));
  1918. // Collapsible details.
  1919. $element['#attached']['library'][] = 'core/drupal.collapse';
  1920. if (!empty($element['#open'])) {
  1921. $element['#attributes']['open'] = 'open';
  1922. }
  1923. // Do not render optional details elements if there are no children.
  1924. if (isset($element['#parents'])) {
  1925. $group = implode('][', $element['#parents']);
  1926. if (!empty($element['#optional']) && !Element::getVisibleChildren($element['#groups'][$group])) {
  1927. $element['#printed'] = TRUE;
  1928. }
  1929. }
  1930. return $element;
  1931. }
  1932. /**
  1933. * Adds members of this group as actual elements for rendering.
  1934. *
  1935. * @param $element
  1936. * An associative array containing the properties and children of the
  1937. * element.
  1938. *
  1939. * @return
  1940. * The modified element with all group members.
  1941. */
  1942. function form_pre_render_group($element) {
  1943. // The element may be rendered outside of a Form API context.
  1944. if (!isset($element['#parents']) || !isset($element['#groups'])) {
  1945. return $element;
  1946. }
  1947. // Inject group member elements belonging to this group.
  1948. $parents = implode('][', $element['#parents']);
  1949. $children = Element::children($element['#groups'][$parents]);
  1950. if (!empty($children)) {
  1951. foreach ($children as $key) {
  1952. // Break references and indicate that the element should be rendered as
  1953. // group member.
  1954. $child = (array) $element['#groups'][$parents][$key];
  1955. $child['#group_details'] = TRUE;
  1956. // Inject the element as new child element.
  1957. $element[] = $child;
  1958. $sort = TRUE;
  1959. }
  1960. // Re-sort the element's children if we injected group member elements.
  1961. if (isset($sort)) {
  1962. $element['#sorted'] = FALSE;
  1963. }
  1964. }
  1965. if (isset($element['#group'])) {
  1966. // Contains form element summary functionalities.
  1967. $element['#attached']['library'][] = 'core/drupal.form';
  1968. $group = $element['#group'];
  1969. // If this element belongs to a group, but the group-holding element does
  1970. // not exist, we need to render it (at its original location).
  1971. if (!isset($element['#groups'][$group]['#group_exists'])) {
  1972. // Intentionally empty to clarify the flow; we simply return $element.
  1973. }
  1974. // If we injected this element into the group, then we want to render it.
  1975. elseif (!empty($element['#group_details'])) {
  1976. // Intentionally empty to clarify the flow; we simply return $element.
  1977. }
  1978. // Otherwise, this element belongs to a group and the group exists, so we do
  1979. // not render it.
  1980. elseif (Element::children($element['#groups'][$group])) {
  1981. $element['#printed'] = TRUE;
  1982. }
  1983. }
  1984. return $element;
  1985. }
  1986. /**
  1987. * Creates a group formatted as vertical tabs.
  1988. *
  1989. * @param $element
  1990. * An associative array containing the properties and children of the
  1991. * details element.
  1992. * @param $form_state
  1993. * The $form_state array for the form this vertical tab widget belongs to.
  1994. *
  1995. * @return
  1996. * The processed element.
  1997. */
  1998. function form_process_vertical_tabs($element, &$form_state) {
  1999. // Inject a new details as child, so that form_process_details() processes
  2000. // this details element like any other details.
  2001. $element['group'] = array(
  2002. '#type' => 'details',
  2003. '#theme_wrappers' => array(),
  2004. '#parents' => $element['#parents'],
  2005. );
  2006. // Add an invisible label for accessibility.
  2007. if (!isset($element['#title'])) {
  2008. $element['#title'] = t('Vertical Tabs');
  2009. $element['#title_display'] = 'invisible';
  2010. }
  2011. $element['#attached']['library'][] = 'core/drupal.vertical-tabs';
  2012. // The JavaScript stores the currently selected tab in this hidden
  2013. // field so that the active tab can be restored the next time the
  2014. // form is rendered, e.g. on preview pages or when form validation
  2015. // fails.
  2016. $name = implode('__', $element['#parents']);
  2017. if (isset($form_state['values'][$name . '__active_tab'])) {
  2018. $element['#default_tab'] = $form_state['values'][$name . '__active_tab'];
  2019. }
  2020. $element[$name . '__active_tab'] = array(
  2021. '#type' => 'hidden',
  2022. '#default_value' => $element['#default_tab'],
  2023. '#attributes' => array('class' => array('vertical-tabs-active-tab')),
  2024. );
  2025. return $element;
  2026. }
  2027. /**
  2028. * Prepares a vertical_tabs element for rendering.
  2029. *
  2030. * @param array $element
  2031. * An associative array containing the properties and children of the
  2032. * vertical tabs element.
  2033. *
  2034. * @return array
  2035. * The modified element.
  2036. */
  2037. function form_pre_render_vertical_tabs($element) {
  2038. // Do not render the vertical tabs element if it is empty.
  2039. $group = implode('][', $element['#parents']);
  2040. if (!Element::getVisibleChildren($element['group']['#groups'][$group])) {
  2041. $element['#printed'] = TRUE;
  2042. }
  2043. return $element;
  2044. }
  2045. /**
  2046. * Prepares variables for vertical tabs templates.
  2047. *
  2048. * Default template: vertical-tabs.html.twig.
  2049. *
  2050. * @param array $variables
  2051. * An associative array containing:
  2052. * - element: An associative array containing the properties and children of
  2053. * the details element. Properties used: #children.
  2054. *
  2055. */
  2056. function template_preprocess_vertical_tabs(&$variables) {
  2057. $element = $variables['element'];
  2058. $variables['children'] = (!empty($element['#children'])) ? $element['#children'] : '';
  2059. }
  2060. /**
  2061. * Adds autocomplete functionality to elements with a valid
  2062. * #autocomplete_route_name.
  2063. *
  2064. * Suppose your autocomplete route name is 'mymodule.autocomplete' and its path
  2065. * is: '/mymodule/autocomplete/{a}/{b}'
  2066. * In your form you have:
  2067. * @code
  2068. * '#autocomplete_route_name' => 'mymodule.autocomplete',
  2069. * '#autocomplete_route_parameters' => array('a' => $some_key, 'b' => $some_id),
  2070. * @endcode
  2071. * The user types in "keywords" so the full path called is:
  2072. * 'mymodule_autocomplete/$some_key/$some_id?q=keywords'
  2073. *
  2074. * @param array $element
  2075. * The form element to process. Properties used:
  2076. * - #autocomplete_route_name: A route to be used as callback URL by the
  2077. * autocomplete JavaScript library.
  2078. * - #autocomplete_route_parameters: The parameters to be used in conjunction
  2079. * with the route name.
  2080. * @param array $form_state
  2081. * An associative array containing the current state of the form.
  2082. *
  2083. * @return array
  2084. * The form element.
  2085. */
  2086. function form_process_autocomplete($element, &$form_state) {
  2087. $access = FALSE;
  2088. if (!empty($element['#autocomplete_route_name'])) {
  2089. $parameters = isset($element['#autocomplete_route_parameters']) ? $element['#autocomplete_route_parameters'] : array();
  2090. $path = \Drupal::urlGenerator()->generate($element['#autocomplete_route_name'], $parameters);
  2091. $access = \Drupal::service('access_manager')->checkNamedRoute($element['#autocomplete_route_name'], $parameters, \Drupal::currentUser());
  2092. }
  2093. if ($access) {
  2094. $element['#attributes']['class'][] = 'form-autocomplete';
  2095. $element['#attached']['library'][] = 'core/drupal.autocomplete';
  2096. // Provide a data attribute for the JavaScript behavior to bind to.
  2097. $element['#attributes']['data-autocomplete-path'] = $path;
  2098. }
  2099. return $element;
  2100. }
  2101. /**
  2102. * Prepares variables for input templates.
  2103. *
  2104. * Default template: input.html.twig.
  2105. *
  2106. * @param array $variables
  2107. * An associative array containing:
  2108. * - element: An associative array containing the properties of the element.
  2109. * Properties used: #attributes.
  2110. */
  2111. function template_preprocess_input(&$variables) {
  2112. $element = $variables['element'];
  2113. $variables['children'] = $element['#children'];
  2114. }
  2115. /**
  2116. * Prepares a #type 'button' render element for theme_input().
  2117. *
  2118. * @param array $element
  2119. * An associative array containing the properties of the element.
  2120. * Properties used: #attributes, #button_type, #name, #value.
  2121. *
  2122. * The #button_type property accepts any value, though core themes have CSS that
  2123. * styles the following button_types appropriately: 'primary', 'danger'.
  2124. *
  2125. * @return array
  2126. * The $element with prepared variables ready for theme_input().
  2127. */
  2128. function form_pre_render_button($element) {
  2129. $element['#attributes']['type'] = 'submit';
  2130. Element::setAttributes($element, array('id', 'name', 'value'));
  2131. $element['#attributes']['class'][] = 'button';
  2132. if (!empty($element['#button_type'])) {
  2133. $element['#attributes']['class'][] = 'button--' . $element['#button_type'];
  2134. }
  2135. // @todo Various JavaScript depends on this button class.
  2136. $element['#attributes']['class'][] = 'form-submit';
  2137. if (!empty($element['#attributes']['disabled'])) {
  2138. $element['#attributes']['class'][] = 'is-disabled';
  2139. }
  2140. return $element;
  2141. }
  2142. /**
  2143. * Prepares a #type 'image_button' render element for theme_input().
  2144. *
  2145. * @param array $element
  2146. * An associative array containing the properties of the element.
  2147. * Properties used: #attributes, #button_type, #name, #value, #title, #src.
  2148. *
  2149. * The #button_type property accepts any value, though core themes have css that
  2150. * styles the following button_types appropriately: 'primary', 'danger'.
  2151. *
  2152. * @return array
  2153. * The $element with prepared variables ready for theme_input().
  2154. */
  2155. function form_pre_render_image_button($element) {
  2156. $element['#attributes']['type'] = 'image';
  2157. Element::setAttributes($element, array('id', 'name', 'value'));
  2158. $element['#attributes']['src'] = file_create_url($element['#src']);
  2159. if (!empty($element['#title'])) {
  2160. $element['#attributes']['alt'] = $element['#title'];
  2161. $element['#attributes']['title'] = $element['#title'];
  2162. }
  2163. $element['#attributes']['class'][] = 'image-button';
  2164. if (!empty($element['#button_type'])) {
  2165. $element['#attributes']['class'][] = 'image-button--' . $element['#button_type'];
  2166. }
  2167. // @todo Various JavaScript depends on this button class.
  2168. $element['#attributes']['class'][] = 'form-submit';
  2169. if (!empty($element['#attributes']['disabled'])) {
  2170. $element['#attributes']['class'][] = 'is-disabled';
  2171. }
  2172. return $element;
  2173. }
  2174. /**
  2175. * Prepares a #type 'hidden' render element for theme_input().
  2176. *
  2177. * @param array $element
  2178. * An associative array containing the properties of the element.
  2179. * Properties used: #name, #value, #attributes.
  2180. *
  2181. * @return array
  2182. * The $element with prepared variables ready for theme_input().
  2183. */
  2184. function form_pre_render_hidden($element) {
  2185. $element['#attributes']['type'] = 'hidden';
  2186. Element::setAttributes($element, array('name', 'value'));
  2187. return $element;
  2188. }
  2189. /**
  2190. * Prepares a #type 'textfield' render element for theme_input().
  2191. *
  2192. * @param array $element
  2193. * An associative array containing the properties of the element.
  2194. * Properties used: #title, #value, #description, #size, #maxlength,
  2195. * #placeholder, #required, #attributes.
  2196. *
  2197. * @return array
  2198. * The $element with prepared variables ready for theme_input().
  2199. */
  2200. function form_pre_render_textfield($element) {
  2201. $element['#attributes']['type'] = 'text';
  2202. Element::setAttributes($element, array('id', 'name', 'value', 'size', 'maxlength', 'placeholder'));
  2203. _form_set_attributes($element, array('form-text'));
  2204. return $element;
  2205. }
  2206. /**
  2207. * Prepares a #type 'email' render element for theme_input().
  2208. *
  2209. * @param array $element
  2210. * An associative array containing the properties of the element.
  2211. * Properties used: #title, #value, #description, #size, #maxlength,
  2212. * #placeholder, #required, #attributes.
  2213. *
  2214. * @return array
  2215. * The $element with prepared variables ready for theme_input().
  2216. */
  2217. function form_pre_render_email($element) {
  2218. $element['#attributes']['type'] = 'email';
  2219. Element::setAttributes($element, array('id', 'name', 'value', 'size', 'maxlength', 'placeholder'));
  2220. _form_set_attributes($element, array('form-email'));
  2221. return $element;
  2222. }
  2223. /**
  2224. * Form element validation handler for #type 'email'.
  2225. *
  2226. * Note that #maxlength and #required is validated by _form_validate() already.
  2227. */
  2228. function form_validate_email(&$element, &$form_state) {
  2229. $value = trim($element['#value']);
  2230. form_set_value($element, $value, $form_state);
  2231. if ($value !== '' && !valid_email_address($value)) {
  2232. form_error($element, $form_state, t('The email address %mail is not valid.', array('%mail' => $value)));
  2233. }
  2234. }
  2235. /**
  2236. * Prepares a #type 'tel' render element for theme_input().
  2237. *
  2238. * @param array $element
  2239. * An associative array containing the properties of the element.
  2240. * Properties used: #title, #value, #description, #size, #maxlength,
  2241. * #placeholder, #required, #attributes.
  2242. *
  2243. * @return array
  2244. * The $element with prepared variables ready for theme_input().
  2245. */
  2246. function form_pre_render_tel($element) {
  2247. $element['#attributes']['type'] = 'tel';
  2248. Element::setAttributes($element, array('id', 'name', 'value', 'size', 'maxlength', 'placeholder'));
  2249. _form_set_attributes($element, array('form-tel'));
  2250. return $element;
  2251. }
  2252. /**
  2253. * Prepares a #type 'number' render element for theme_input().
  2254. *
  2255. * @param array $element
  2256. * An associative array containing the properties of the element.
  2257. * Properties used: #title, #value, #description, #min, #max, #placeholder,
  2258. * #required, #attributes, #step, #size.
  2259. *
  2260. * @return array
  2261. * The $element with prepared variables ready for theme_input().
  2262. */
  2263. function form_pre_render_number($element) {
  2264. $element['#attributes']['type'] = 'number';
  2265. Element::setAttributes($element, array('id', 'name', 'value', 'step', 'min', 'max', 'placeholder', 'size'));
  2266. _form_set_attributes($element, array('form-number'));
  2267. return $element;
  2268. }
  2269. /**
  2270. * Prepares a #type 'range' render element for theme_input().
  2271. *
  2272. * @param array $element
  2273. * An associative array containing the properties of the element.
  2274. * Properties used: #title, #value, #description, #min, #max, #attributes,
  2275. * #step.
  2276. *
  2277. * @return array
  2278. * The $element with prepared variables ready for theme_input().
  2279. */
  2280. function form_pre_render_range($element) {
  2281. $element['#attributes']['type'] = 'range';
  2282. Element::setAttributes($element, array('id', 'name', 'value', 'step', 'min', 'max'));
  2283. _form_set_attributes($element, array('form-range'));
  2284. return $element;
  2285. }
  2286. /**
  2287. * Form element validation handler for #type 'number'.
  2288. *
  2289. * Note that #required is validated by _form_validate() already.
  2290. */
  2291. function form_validate_number(&$element, &$form_state) {
  2292. $value = $element['#value'];
  2293. if ($value === '') {
  2294. return;
  2295. }
  2296. $name = empty($element['#title']) ? $element['#parents'][0] : $element['#title'];
  2297. // Ensure the input is numeric.
  2298. if (!is_numeric($value)) {
  2299. form_error($element, $form_state, t('%name must be a number.', array('%name' => $name)));
  2300. return;
  2301. }
  2302. // Ensure that the input is greater than the #min property, if set.
  2303. if (isset($element['#min']) && $value < $element['#min']) {
  2304. form_error($element, $form_state, t('%name must be higher than or equal to %min.', array('%name' => $name, '%min' => $element['#min'])));
  2305. }
  2306. // Ensure that the input is less than the #max property, if set.
  2307. if (isset($element['#max']) && $value > $element['#max']) {
  2308. form_error($element, $form_state, t('%name must be lower than or equal to %max.', array('%name' => $name, '%max' => $element['#max'])));
  2309. }
  2310. if (isset($element['#step']) && strtolower($element['#step']) != 'any') {
  2311. // Check that the input is an allowed multiple of #step (offset by #min if
  2312. // #min is set).
  2313. $offset = isset($element['#min']) ? $element['#min'] : 0.0;
  2314. if (!Number::validStep($value, $element['#step'], $offset)) {
  2315. form_error($element, $form_state, t('%name is not a valid number.', array('%name' => $name)));
  2316. }
  2317. }
  2318. }
  2319. /**
  2320. * Determines the value for a range element.
  2321. *
  2322. * Make sure range elements always have a value. The 'required' attribute is not
  2323. * allowed for range elements.
  2324. *
  2325. * @param $element
  2326. * The form element whose value is being populated.
  2327. * @param $input
  2328. * The incoming input to populate the form element. If this is FALSE, the
  2329. * element's default value should be returned.
  2330. *
  2331. * @return
  2332. * The data that will appear in the $form_state['values'] collection for
  2333. * this element. Return nothing to use the default.
  2334. */
  2335. function form_type_range_value($element, $input = FALSE) {
  2336. if ($input === '') {
  2337. $offset = ($element['#max'] - $element['#min']) / 2;
  2338. // Round to the step.
  2339. if (strtolower($element['#step']) != 'any') {
  2340. $steps = round($offset / $element['#step']);
  2341. $offset = $element['#step'] * $steps;
  2342. }
  2343. return $element['#min'] + $offset;
  2344. }
  2345. }
  2346. /**
  2347. * Prepares a #type 'url' render element for theme_input().
  2348. *
  2349. * @param array $element
  2350. * An associative array containing the properties of the element.
  2351. * Properties used: #title, #value, #description, #size, #maxlength,
  2352. * #placeholder, #required, #attributes.
  2353. *
  2354. * @return array
  2355. * The $element with prepared variables ready for theme_input().
  2356. */
  2357. function form_pre_render_url($element) {
  2358. $element['#attributes']['type'] = 'url';
  2359. Element::setAttributes($element, array('id', 'name', 'value', 'size', 'maxlength', 'placeholder'));
  2360. _form_set_attributes($element, array('form-url'));
  2361. return $element;
  2362. }
  2363. /**
  2364. * Prepares a #type 'search' render element for theme_input().
  2365. *
  2366. * @param array $element
  2367. * An associative array containing the properties of the element.
  2368. * Properties used: #title, #value, #description, #size, #maxlength,
  2369. * #placeholder, #required, #attributes.
  2370. *
  2371. * @return array
  2372. * The $element with prepared variables ready for theme_input().
  2373. */
  2374. function form_pre_render_search($element) {
  2375. $element['#attributes']['type'] = 'search';
  2376. Element::setAttributes($element, array('id', 'name', 'value', 'size', 'maxlength', 'placeholder'));
  2377. _form_set_attributes($element, array('form-search'));
  2378. return $element;
  2379. }
  2380. /**
  2381. * Form element validation handler for #type 'url'.
  2382. *
  2383. * Note that #maxlength and #required is validated by _form_validate() already.
  2384. */
  2385. function form_validate_url(&$element, &$form_state) {
  2386. $value = trim($element['#value']);
  2387. form_set_value($element, $value, $form_state);
  2388. if ($value !== '' && !UrlHelper::isValid($value, TRUE)) {
  2389. form_error($element, $form_state, t('The URL %url is not valid.', array('%url' => $value)));
  2390. }
  2391. }
  2392. /**
  2393. * Form element validation handler for #type 'color'.
  2394. */
  2395. function form_validate_color(&$element, &$form_state) {
  2396. $value = trim($element['#value']);
  2397. // Default to black if no value is given.
  2398. // @see http://www.w3.org/TR/html5/number-state.html#color-state
  2399. if ($value === '') {
  2400. form_set_value($element, '#000000', $form_state);
  2401. }
  2402. else {
  2403. // Try to parse the value and normalize it.
  2404. try {
  2405. form_set_value($element, Color::rgbToHex(Color::hexToRgb($value)), $form_state);
  2406. }
  2407. catch (InvalidArgumentException $e) {
  2408. form_error($element, $form_state, t('%name must be a valid color.', array('%name' => empty($element['#title']) ? $element['#parents'][0] : $element['#title'])));
  2409. }
  2410. }
  2411. }
  2412. /**
  2413. * Prepares a #type 'color' render element for theme_input().
  2414. *
  2415. * @param array $element
  2416. * An associative array containing the properties of the element.
  2417. * Properties used: #title, #value, #description, #attributes.
  2418. *
  2419. * @return array
  2420. * The $element with prepared variables ready for theme_input().
  2421. */
  2422. function form_pre_render_color($element) {
  2423. $element['#attributes']['type'] = 'color';
  2424. Element::setAttributes($element, array('id', 'name', 'value'));
  2425. _form_set_attributes($element, array('form-color'));
  2426. return $element;
  2427. }
  2428. /**
  2429. * Prepares variables for form templates.
  2430. *
  2431. * Default template: form.html.twig.
  2432. *
  2433. * @param $variables
  2434. * An associative array containing:
  2435. * - element: An associative array containing the properties of the element.
  2436. * Properties used: #action, #method, #attributes, #children
  2437. */
  2438. function template_preprocess_form(&$variables) {
  2439. $element = $variables['element'];
  2440. if (isset($element['#action'])) {
  2441. $element['#attributes']['action'] = UrlHelper::stripDangerousProtocols($element['#action']);
  2442. }
  2443. Element::setAttributes($element, array('method', 'id'));
  2444. if (empty($element['#attributes']['accept-charset'])) {
  2445. $element['#attributes']['accept-charset'] = "UTF-8";
  2446. }
  2447. $variables['attributes'] = $element['#attributes'];
  2448. $variables['children'] = $element['#children'];
  2449. }
  2450. /**
  2451. * Prepares variables for textarea templates.
  2452. *
  2453. * Default template: textarea.html.twig.
  2454. *
  2455. * @param array $variables
  2456. * An associative array containing:
  2457. * - element: An associative array containing the properties of the element.
  2458. * Properties used: #title, #value, #description, #rows, #cols,
  2459. * #placeholder, #required, #attributes, #resizable
  2460. *
  2461. */
  2462. function template_preprocess_textarea(&$variables) {
  2463. $element = $variables['element'];
  2464. Element::setAttributes($element, array('id', 'name', 'rows', 'cols', 'placeholder'));
  2465. _form_set_attributes($element, array('form-textarea'));
  2466. $variables['wrapper_attributes'] = new Attribute(array(
  2467. 'class' => array('form-textarea-wrapper'),
  2468. ));
  2469. // Add resizable behavior.
  2470. if (!empty($element['#resizable'])) {
  2471. $element['#attributes']['class'][] = 'resize-' . $element['#resizable'];
  2472. }
  2473. $variables['attributes'] = new Attribute($element['#attributes']);
  2474. $variables['value'] = String::checkPlain($element['#value']);
  2475. }
  2476. /**
  2477. * Prepares a #type 'password' render element for theme_input().
  2478. *
  2479. * @param array $element
  2480. * An associative array containing the properties of the element.
  2481. * Properties used: #title, #value, #description, #size, #maxlength,
  2482. * #placeholder, #required, #attributes.
  2483. *
  2484. * @return array
  2485. * The $element with prepared variables ready for theme_input().
  2486. */
  2487. function form_pre_render_password($element) {
  2488. $element['#attributes']['type'] = 'password';
  2489. Element::setAttributes($element, array('id', 'name', 'size', 'maxlength', 'placeholder'));
  2490. _form_set_attributes($element, array('form-text'));
  2491. return $element;
  2492. }
  2493. /**
  2494. * Expands a weight element into a select element.
  2495. */
  2496. function form_process_weight($element) {
  2497. $element['#is_weight'] = TRUE;
  2498. // If the number of options is small enough, use a select field.
  2499. $max_elements = \Drupal::config('system.site')->get('weight_select_max');
  2500. if ($element['#delta'] <= $max_elements) {
  2501. $element['#type'] = 'select';
  2502. for ($n = (-1 * $element['#delta']); $n <= $element['#delta']; $n++) {
  2503. $weights[$n] = $n;
  2504. }
  2505. $element['#options'] = $weights;
  2506. $element += element_info('select');
  2507. }
  2508. // Otherwise, use a text field.
  2509. else {
  2510. $element['#type'] = 'number';
  2511. // Use a field big enough to fit most weights.
  2512. $element['#size'] = 10;
  2513. $element += element_info('number');
  2514. }
  2515. return $element;
  2516. }
  2517. /**
  2518. * Prepares a #type 'file' render element for theme_input().
  2519. *
  2520. * For assistance with handling the uploaded file correctly, see the API
  2521. * provided by file.inc.
  2522. *
  2523. * @param array $element
  2524. * An associative array containing the properties of the element.
  2525. * Properties used: #title, #name, #size, #description, #required,
  2526. * #attributes.
  2527. *
  2528. * @return array
  2529. * The $element with prepared variables ready for theme_input().
  2530. */
  2531. function form_pre_render_file($element) {
  2532. $element['#attributes']['type'] = 'file';
  2533. Element::setAttributes($element, array('id', 'name', 'size'));
  2534. _form_set_attributes($element, array('form-file'));
  2535. return $element;
  2536. }
  2537. /**
  2538. * Processes a file upload element, make use of #multiple if present.
  2539. */
  2540. function form_process_file($element) {
  2541. if ($element['#multiple']) {
  2542. $element['#attributes'] = array('multiple' => 'multiple');
  2543. $element['#name'] .= '[]';
  2544. }
  2545. return $element;
  2546. }
  2547. /**
  2548. * Returns HTML for a form element.
  2549. * Prepares variables for form element templates.
  2550. *
  2551. * Default template: form-element.html.twig.
  2552. *
  2553. * Each form element is wrapped in a DIV container having the following CSS
  2554. * classes:
  2555. * - form-item: Generic for all form elements.
  2556. * - form-type-#type: The internal element #type.
  2557. * - form-item-#name: The internal form element #name (usually derived from the
  2558. * $form structure and set via form_builder()).
  2559. * - form-disabled: Only set if the form element is #disabled.
  2560. *
  2561. * In addition to the element itself, the DIV contains a label for the element
  2562. * based on the optional #title_display property, and an optional #description.
  2563. *
  2564. * The optional #title_display property can have these values:
  2565. * - before: The label is output before the element. This is the default.
  2566. * The label includes the #title and the required marker, if #required.
  2567. * - after: The label is output after the element. For example, this is used
  2568. * for radio and checkbox #type elements as set in system_element_info().
  2569. * If the #title is empty but the field is #required, the label will
  2570. * contain only the required marker.
  2571. * - invisible: Labels are critical for screen readers to enable them to
  2572. * properly navigate through forms but can be visually distracting. This
  2573. * property hides the label for everyone except screen readers.
  2574. * - attribute: Set the title attribute on the element to create a tooltip
  2575. * but output no label element. This is supported only for checkboxes
  2576. * and radios in form_pre_render_conditional_form_element(). It is used
  2577. * where a visual label is not needed, such as a table of checkboxes where
  2578. * the row and column provide the context. The tooltip will include the
  2579. * title and required marker.
  2580. *
  2581. * If the #title property is not set, then the label and any required marker
  2582. * will not be output, regardless of the #title_display or #required values.
  2583. * This can be useful in cases such as the password_confirm element, which
  2584. * creates children elements that have their own labels and required markers,
  2585. * but the parent element should have neither. Use this carefully because a
  2586. * field without an associated label can cause accessibility challenges.
  2587. *
  2588. * @param array $variables
  2589. * An associative array containing:
  2590. * - element: An associative array containing the properties of the element.
  2591. * Properties used: #title, #title_display, #description, #id, #required,
  2592. * #children, #type, #name.
  2593. */
  2594. function template_preprocess_form_element(&$variables) {
  2595. $element = &$variables['element'];
  2596. // This function is invoked as theme wrapper, but the rendered form element
  2597. // may not necessarily have been processed by form_builder().
  2598. $element += array(
  2599. '#title_display' => 'before',
  2600. );
  2601. // Take over any #wrapper_attributes defined by the element.
  2602. // @todo Temporary hack for #type 'item'.
  2603. // @see http://drupal.org/node/1829202
  2604. $variables['attributes'] = array();
  2605. if (isset($element['#wrapper_attributes'])) {
  2606. $variables['attributes'] = $element['#wrapper_attributes'];
  2607. }
  2608. // Add element #id for #type 'item'.
  2609. if (isset($element['#markup']) && !empty($element['#id'])) {
  2610. $variables['attributes']['id'] = $element['#id'];
  2611. }
  2612. // Add element's #type and #name as class to aid with JS/CSS selectors.
  2613. $variables['attributes']['class'][] = 'form-item';
  2614. if (!empty($element['#type'])) {
  2615. $variables['attributes']['class'][] = 'form-type-' . strtr($element['#type'], '_', '-');
  2616. }
  2617. if (!empty($element['#name'])) {
  2618. $variables['attributes']['class'][] = 'form-item-' . strtr($element['#name'], array(' ' => '-', '_' => '-', '[' => '-', ']' => ''));
  2619. }
  2620. // Add a class for disabled elements to facilitate cross-browser styling.
  2621. if (!empty($element['#attributes']['disabled'])) {
  2622. $variables['attributes']['class'][] = 'form-disabled';
  2623. }
  2624. // If #title is not set, we don't display any label.
  2625. if (!isset($element['#title'])) {
  2626. $element['#title_display'] = 'none';
  2627. }
  2628. // If #title_dislay is not some of the visible options, add a CSS class.
  2629. if ($element['#title_display'] != 'before' && $element['#title_display'] != 'after') {
  2630. $variables['attributes']['class'][] = 'form-no-label';
  2631. }
  2632. $variables['prefix'] = isset($element['#field_prefix']) ? $element['#field_prefix'] : NULL;
  2633. $variables['suffix'] = isset($element['#field_suffix']) ? $element['#field_suffix'] : NULL;
  2634. $variables['description'] = NULL;
  2635. if (!empty($element['#description'])) {
  2636. $description_attributes = array('class' => 'description');
  2637. if (!empty($element['#id'])) {
  2638. $description_attributes['id'] = $element['#id'] . '--description';
  2639. }
  2640. $variables['description']['attributes'] = new Attribute($description_attributes);
  2641. $variables['description']['content'] = $element['#description'];
  2642. }
  2643. // Add label_display and label variables to template.
  2644. $variables['label_display'] = $element['#title_display'];
  2645. $variables['label'] = array('#theme' => 'form_element_label');
  2646. $variables['label'] += array_intersect_key($element, array_flip(array('#id', '#required', '#title', '#title_display')));
  2647. $variables['children'] = $element['#children'];
  2648. }
  2649. /**
  2650. * Prepares variables for form label templates.
  2651. *
  2652. * Form element labels include the #title and a #required marker. The label is
  2653. * associated with the element itself by the element #id. Labels may appear
  2654. * before or after elements, depending on theme_form_element() and
  2655. * #title_display.
  2656. *
  2657. * This function will not be called for elements with no labels, depending on
  2658. * #title_display. For elements that have an empty #title and are not required,
  2659. * this function will output no label (''). For required elements that have an
  2660. * empty #title, this will output the required marker alone within the label.
  2661. * The label will use the #id to associate the marker with the field that is
  2662. * required. That is especially important for screenreader users to know
  2663. * which field is required.
  2664. *
  2665. * @param array $variables
  2666. * An associative array containing:
  2667. * - element: An associative array containing the properties of the element.
  2668. * Properties used: #required, #title, #id, #value, #description.
  2669. */
  2670. function template_preprocess_form_element_label(&$variables) {
  2671. $element = $variables['element'];
  2672. // If title and required marker are both empty, output no label.
  2673. $variables['title'] = (isset($element['#title']) && $element['#title'] !== '') ? Xss::filterAdmin($element['#title']) : '';
  2674. $variables['attributes'] = array();
  2675. // Style the label as class option to display inline with the element.
  2676. if ($element['#title_display'] == 'after') {
  2677. $variables['attributes']['class'][] = 'option';
  2678. }
  2679. // Show label only to screen readers to avoid disruption in visual flows.
  2680. elseif ($element['#title_display'] == 'invisible') {
  2681. $variables['attributes']['class'][] = 'visually-hidden';
  2682. }
  2683. // A #for property of a dedicated #type 'label' element as precedence.
  2684. if (!empty($element['#for'])) {
  2685. $variables['attributes']['for'] = $element['#for'];
  2686. // A custom #id allows the referenced form input element to refer back to
  2687. // the label element; e.g., in the 'aria-labelledby' attribute.
  2688. if (!empty($element['#id'])) {
  2689. $variables['attributes']['id'] = $element['#id'];
  2690. }
  2691. }
  2692. // Otherwise, point to the #id of the form input element.
  2693. elseif (!empty($element['#id'])) {
  2694. $variables['attributes']['for'] = $element['#id'];
  2695. }
  2696. // For required elements a 'form-required' class is appended to the
  2697. // label attributes.
  2698. $variables['required'] = FALSE;
  2699. if (!empty($element['#required'])) {
  2700. $variables['required'] = TRUE;
  2701. $variables['attributes']['class'][] = 'form-required';
  2702. }
  2703. }
  2704. /**
  2705. * Sets a form element's class attribute.
  2706. *
  2707. * Adds 'required' and 'error' classes as needed.
  2708. *
  2709. * @param $element
  2710. * The form element.
  2711. * @param $name
  2712. * Array of new class names to be added.
  2713. */
  2714. function _form_set_attributes(&$element, $class = array()) {
  2715. if (!empty($class)) {
  2716. if (!isset($element['#attributes']['class'])) {
  2717. $element['#attributes']['class'] = array();
  2718. }
  2719. $element['#attributes']['class'] = array_merge($element['#attributes']['class'], $class);
  2720. }
  2721. // This function is invoked from form element theme functions, but the
  2722. // rendered form element may not necessarily have been processed by
  2723. // form_builder().
  2724. if (!empty($element['#required'])) {
  2725. $element['#attributes']['class'][] = 'required';
  2726. $element['#attributes']['required'] = 'required';
  2727. $element['#attributes']['aria-required'] = 'true';
  2728. }
  2729. if (isset($element['#parents']) && isset($element['#errors']) && !empty($element['#validated'])) {
  2730. $element['#attributes']['class'][] = 'error';
  2731. $element['#attributes']['aria-invalid'] = 'true';
  2732. }
  2733. }
  2734. /**
  2735. * @} End of "defgroup form_api".
  2736. */
  2737. /**
  2738. * @defgroup batch Batch operations
  2739. * @{
  2740. * Creates and processes batch operations.
  2741. *
  2742. * Functions allowing forms processing to be spread out over several page
  2743. * requests, thus ensuring that the processing does not get interrupted
  2744. * because of a PHP timeout, while allowing the user to receive feedback
  2745. * on the progress of the ongoing operations.
  2746. *
  2747. * The API is primarily designed to integrate nicely with the Form API
  2748. * workflow, but can also be used by non-Form API scripts (like update.php)
  2749. * or even simple page callbacks (which should probably be used sparingly).
  2750. *
  2751. * Example:
  2752. * @code
  2753. * $batch = array(
  2754. * 'title' => t('Exporting'),
  2755. * 'operations' => array(
  2756. * array('my_function_1', array($account->id(), 'story')),
  2757. * array('my_function_2', array()),
  2758. * ),
  2759. * 'finished' => 'my_finished_callback',
  2760. * 'file' => 'path_to_file_containing_myfunctions',
  2761. * );
  2762. * batch_set($batch);
  2763. * // Only needed if not inside a form _submit handler.
  2764. * // Setting redirect in batch_process.
  2765. * batch_process('node/1');
  2766. * @endcode
  2767. *
  2768. * Note: if the batch 'title', 'init_message', 'progress_message', or
  2769. * 'error_message' could contain any user input, it is the responsibility of
  2770. * the code calling batch_set() to sanitize them first with a function like
  2771. * \Drupal\Component\Utility\String::checkPlain() or
  2772. * \Drupal\Component\Utility\Xss::filter(). Furthermore, if the batch operation
  2773. * returns any user input in the 'results' or 'message' keys of $context, it
  2774. * must also sanitize them first.
  2775. *
  2776. * Sample callback_batch_operation():
  2777. * @code
  2778. * // Simple and artificial: load a node of a given type for a given user
  2779. * function my_function_1($uid, $type, &$context) {
  2780. * // The $context array gathers batch context information about the execution (read),
  2781. * // as well as 'return values' for the current operation (write)
  2782. * // The following keys are provided :
  2783. * // 'results' (read / write): The array of results gathered so far by
  2784. * // the batch processing, for the current operation to append its own.
  2785. * // 'message' (write): A text message displayed in the progress page.
  2786. * // The following keys allow for multi-step operations :
  2787. * // 'sandbox' (read / write): An array that can be freely used to
  2788. * // store persistent data between iterations. It is recommended to
  2789. * // use this instead of $_SESSION, which is unsafe if the user
  2790. * // continues browsing in a separate window while the batch is processing.
  2791. * // 'finished' (write): A float number between 0 and 1 informing
  2792. * // the processing engine of the completion level for the operation.
  2793. * // 1 (or no value explicitly set) means the operation is finished
  2794. * // and the batch processing can continue to the next operation.
  2795. *
  2796. * $nodes = entity_load_multiple_by_properties('node', array('uid' => $uid, 'type' => $type));
  2797. * $node = reset($nodes);
  2798. * $context['results'][] = $node->id() . ' : ' . String::checkPlain($node->label());
  2799. * $context['message'] = String::checkPlain($node->label());
  2800. * }
  2801. *
  2802. * // A more advanced example is a multi-step operation that loads all rows,
  2803. * // five by five.
  2804. * function my_function_2(&$context) {
  2805. * if (empty($context['sandbox'])) {
  2806. * $context['sandbox']['progress'] = 0;
  2807. * $context['sandbox']['current_id'] = 0;
  2808. * $context['sandbox']['max'] = db_query('SELECT COUNT(DISTINCT id) FROM {example}')->fetchField();
  2809. * }
  2810. * $limit = 5;
  2811. * $result = db_select('example')
  2812. * ->fields('example', array('id'))
  2813. * ->condition('id', $context['sandbox']['current_id'], '>')
  2814. * ->orderBy('id')
  2815. * ->range(0, $limit)
  2816. * ->execute();
  2817. * foreach ($result as $row) {
  2818. * $context['results'][] = $row->id . ' : ' . String::checkPlain($row->title);
  2819. * $context['sandbox']['progress']++;
  2820. * $context['sandbox']['current_id'] = $row->id;
  2821. * $context['message'] = String::checkPlain($row->title);
  2822. * }
  2823. * if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
  2824. * $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
  2825. * }
  2826. * }
  2827. * @endcode
  2828. *
  2829. * Sample callback_batch_finished():
  2830. * @code
  2831. * function batch_test_finished($success, $results, $operations) {
  2832. * // The 'success' parameter means no fatal PHP errors were detected. All
  2833. * // other error management should be handled using 'results'.
  2834. * if ($success) {
  2835. * $message = format_plural(count($results), 'One post processed.', '@count posts processed.');
  2836. * }
  2837. * else {
  2838. * $message = t('Finished with an error.');
  2839. * }
  2840. * drupal_set_message($message);
  2841. * // Providing data for the redirected page is done through $_SESSION.
  2842. * foreach ($results as $result) {
  2843. * $items[] = t('Loaded node %title.', array('%title' => $result));
  2844. * }
  2845. * $_SESSION['my_batch_results'] = $items;
  2846. * }
  2847. * @endcode
  2848. */
  2849. /**
  2850. * Adds a new batch.
  2851. *
  2852. * Batch operations are added as new batch sets. Batch sets are used to spread
  2853. * processing (primarily, but not exclusively, forms processing) over several
  2854. * page requests. This helps to ensure that the processing is not interrupted
  2855. * due to PHP timeouts, while users are still able to receive feedback on the
  2856. * progress of the ongoing operations. Combining related operations into
  2857. * distinct batch sets provides clean code independence for each batch set,
  2858. * ensuring that two or more batches, submitted independently, can be processed
  2859. * without mutual interference. Each batch set may specify its own set of
  2860. * operations and results, produce its own UI messages, and trigger its own
  2861. * 'finished' callback. Batch sets are processed sequentially, with the progress
  2862. * bar starting afresh for each new set.
  2863. *
  2864. * @param $batch_definition
  2865. * An associative array defining the batch, with the following elements (all
  2866. * are optional except as noted):
  2867. * - operations: (required) Array of operations to be performed, where each
  2868. * item is an array consisting of the name of an implementation of
  2869. * callback_batch_operation() and an array of parameter.
  2870. * Example:
  2871. * @code
  2872. * array(
  2873. * array('callback_batch_operation_1', array($arg1)),
  2874. * array('callback_batch_operation_2', array($arg2_1, $arg2_2)),
  2875. * )
  2876. * @endcode
  2877. * - title: A safe, translated string to use as the title for the progress
  2878. * page. Defaults to t('Processing').
  2879. * - init_message: Message displayed while the processing is initialized.
  2880. * Defaults to t('Initializing.').
  2881. * - progress_message: Message displayed while processing the batch. Available
  2882. * placeholders are @current, @remaining, @total, @percentage, @estimate and
  2883. * @elapsed. Defaults to t('Completed @current of @total.').
  2884. * - error_message: Message displayed if an error occurred while processing
  2885. * the batch. Defaults to t('An error has occurred.').
  2886. * - finished: Name of an implementation of callback_batch_finished(). This is
  2887. * executed after the batch has completed. This should be used to perform
  2888. * any result massaging that may be needed, and possibly save data in
  2889. * $_SESSION for display after final page redirection.
  2890. * - file: Path to the file containing the definitions of the 'operations' and
  2891. * 'finished' functions, for instance if they don't reside in the main
  2892. * .module file. The path should be relative to base_path(), and thus should
  2893. * be built using drupal_get_path().
  2894. * - css: Array of paths to CSS files to be used on the progress page.
  2895. * - url_options: options passed to url() when constructing redirect URLs for
  2896. * the batch.
  2897. * - safe_strings: Internal use only. Used to store and retrieve strings
  2898. * marked as safe between requests.
  2899. */
  2900. function batch_set($batch_definition) {
  2901. if ($batch_definition) {
  2902. $batch =& batch_get();
  2903. // Initialize the batch if needed.
  2904. if (empty($batch)) {
  2905. $batch = array(
  2906. 'sets' => array(),
  2907. 'has_form_submits' => FALSE,
  2908. );
  2909. }
  2910. // Base and default properties for the batch set.
  2911. $init = array(
  2912. 'sandbox' => array(),
  2913. 'results' => array(),
  2914. 'success' => FALSE,
  2915. 'start' => 0,
  2916. 'elapsed' => 0,
  2917. );
  2918. $defaults = array(
  2919. 'title' => t('Processing'),
  2920. 'init_message' => t('Initializing.'),
  2921. 'progress_message' => t('Completed @current of @total.'),
  2922. 'error_message' => t('An error has occurred.'),
  2923. 'css' => array(),
  2924. );
  2925. $batch_set = $init + $batch_definition + $defaults;
  2926. // Tweak init_message to avoid the bottom of the page flickering down after
  2927. // init phase.
  2928. $batch_set['init_message'] .= '<br/>&nbsp;';
  2929. // The non-concurrent workflow of batch execution allows us to save
  2930. // numberOfItems() queries by handling our own counter.
  2931. $batch_set['total'] = count($batch_set['operations']);
  2932. $batch_set['count'] = $batch_set['total'];
  2933. // Add the set to the batch.
  2934. if (empty($batch['id'])) {
  2935. // The batch is not running yet. Simply add the new set.
  2936. $batch['sets'][] = $batch_set;
  2937. }
  2938. else {
  2939. // The set is being added while the batch is running. Insert the new set
  2940. // right after the current one to ensure execution order, and store its
  2941. // operations in a queue.
  2942. $index = $batch['current_set'] + 1;
  2943. $slice1 = array_slice($batch['sets'], 0, $index);
  2944. $slice2 = array_slice($batch['sets'], $index);
  2945. $batch['sets'] = array_merge($slice1, array($batch_set), $slice2);
  2946. _batch_populate_queue($batch, $index);
  2947. }
  2948. }
  2949. }
  2950. /**
  2951. * Processes the batch.
  2952. *
  2953. * This function is generally not needed in form submit handlers;
  2954. * Form API takes care of batches that were set during form submission.
  2955. *
  2956. * @param $redirect
  2957. * (optional) Path to redirect to when the batch has finished processing.
  2958. * @param $url
  2959. * (optional - should only be used for separate scripts like update.php)
  2960. * URL of the batch processing page.
  2961. * @param $redirect_callback
  2962. * (optional) Specify a function to be called to redirect to the progressive
  2963. * processing page.
  2964. *
  2965. * @return \Symfony\Component\HttpFoundation\RedirectResponse|null
  2966. * A redirect response if the batch is progressive. No return value otherwise.
  2967. */
  2968. function batch_process($redirect = NULL, $url = 'batch', $redirect_callback = NULL) {
  2969. $batch =& batch_get();
  2970. drupal_theme_initialize();
  2971. if (isset($batch)) {
  2972. // Add process information
  2973. $process_info = array(
  2974. 'current_set' => 0,
  2975. 'progressive' => TRUE,
  2976. 'url' => $url,
  2977. 'url_options' => array(),
  2978. 'source_url' => current_path(),
  2979. 'redirect' => $redirect,
  2980. 'theme' => $GLOBALS['theme_key'],
  2981. 'redirect_callback' => $redirect_callback,
  2982. );
  2983. $batch += $process_info;
  2984. // The batch is now completely built. Allow other modules to make changes
  2985. // to the batch so that it is easier to reuse batch processes in other
  2986. // environments.
  2987. \Drupal::moduleHandler()->alter('batch', $batch);
  2988. // Assign an arbitrary id: don't rely on a serial column in the 'batch'
  2989. // table, since non-progressive batches skip database storage completely.
  2990. $batch['id'] = db_next_id();
  2991. // Move operations to a job queue. Non-progressive batches will use a
  2992. // memory-based queue.
  2993. foreach ($batch['sets'] as $key => $batch_set) {
  2994. _batch_populate_queue($batch, $key);
  2995. }
  2996. // Initiate processing.
  2997. if ($batch['progressive']) {
  2998. // Now that we have a batch id, we can generate the redirection link in
  2999. // the generic error message.
  3000. $batch['error_message'] = t('Please continue to <a href="@error_url">the error page</a>', array('@error_url' => url($url, array('query' => array('id' => $batch['id'], 'op' => 'finished')))));
  3001. // Clear the way for the redirection to the batch processing page, by
  3002. // saving and unsetting the 'destination', if there is any.
  3003. $request = \Drupal::request();
  3004. if ($request->query->has('destination')) {
  3005. $batch['destination'] = $request->query->get('destination');
  3006. $request->query->remove('destination');
  3007. }
  3008. // Store safe strings.
  3009. // @todo Ensure we are not storing an excessively large string list in:
  3010. // https://www.drupal.org/node/2295823
  3011. $batch['safe_strings'] = SafeMarkup::getAll();
  3012. // Store the batch.
  3013. \Drupal::service('batch.storage')->create($batch);
  3014. // Set the batch number in the session to guarantee that it will stay alive.
  3015. $_SESSION['batches'][$batch['id']] = TRUE;
  3016. // Redirect for processing.
  3017. $options = array('query' => array('op' => 'start', 'id' => $batch['id']));
  3018. if (($function = $batch['redirect_callback']) && function_exists($function)) {
  3019. $function($batch['url'], $options);
  3020. }
  3021. else {
  3022. $options['absolute'] = TRUE;
  3023. return new RedirectResponse(url($batch['url'], $options));
  3024. }
  3025. }
  3026. else {
  3027. // Non-progressive execution: bypass the whole progressbar workflow
  3028. // and execute the batch in one pass.
  3029. require_once __DIR__ . '/batch.inc';
  3030. _batch_process();
  3031. }
  3032. }
  3033. }
  3034. /**
  3035. * Retrieves the current batch.
  3036. */
  3037. function &batch_get() {
  3038. // Not drupal_static(), because Batch API operates at a lower level than most
  3039. // use-cases for resetting static variables, and we specifically do not want a
  3040. // global drupal_static_reset() resetting the batch information. Functions
  3041. // that are part of the Batch API and need to reset the batch information may
  3042. // call batch_get() and manipulate the result by reference. Functions that are
  3043. // not part of the Batch API can also do this, but shouldn't.
  3044. static $batch = array();
  3045. return $batch;
  3046. }
  3047. /**
  3048. * Populates a job queue with the operations of a batch set.
  3049. *
  3050. * Depending on whether the batch is progressive or not, the
  3051. * Drupal\Core\Queue\Batch or Drupal\Core\Queue\BatchMemory handler classes will
  3052. * be used.
  3053. *
  3054. * @param $batch
  3055. * The batch array.
  3056. * @param $set_id
  3057. * The id of the set to process.
  3058. *
  3059. * @return
  3060. * The name and class of the queue are added by reference to the batch set.
  3061. */
  3062. function _batch_populate_queue(&$batch, $set_id) {
  3063. $batch_set = &$batch['sets'][$set_id];
  3064. if (isset($batch_set['operations'])) {
  3065. $batch_set += array(
  3066. 'queue' => array(
  3067. 'name' => 'drupal_batch:' . $batch['id'] . ':' . $set_id,
  3068. 'class' => $batch['progressive'] ? 'Drupal\Core\Queue\Batch' : 'Drupal\Core\Queue\BatchMemory',
  3069. ),
  3070. );
  3071. $queue = _batch_queue($batch_set);
  3072. $queue->createQueue();
  3073. foreach ($batch_set['operations'] as $operation) {
  3074. $queue->createItem($operation);
  3075. }
  3076. unset($batch_set['operations']);
  3077. }
  3078. }
  3079. /**
  3080. * Returns a queue object for a batch set.
  3081. *
  3082. * @param $batch_set
  3083. * The batch set.
  3084. *
  3085. * @return
  3086. * The queue object.
  3087. */
  3088. function _batch_queue($batch_set) {
  3089. static $queues;
  3090. if (!isset($queues)) {
  3091. $queues = array();
  3092. }
  3093. if (isset($batch_set['queue'])) {
  3094. $name = $batch_set['queue']['name'];
  3095. $class = $batch_set['queue']['class'];
  3096. if (!isset($queues[$class][$name])) {
  3097. $queues[$class][$name] = new $class($name, Database::getConnection());
  3098. }
  3099. return $queues[$class][$name];
  3100. }
  3101. }
  3102. /**
  3103. * @} End of "defgroup batch".
  3104. */