Newer
Older

Dries Buytaert
committed
/**
* @file
* Functions for form and batch generation and processing.
*/

Dries Buytaert
committed
use Drupal\Component\Utility\NestedArray;
use Drupal\Component\Utility\SafeMarkup;

Angie Byron
committed
use Drupal\Component\Utility\String;

Dries Buytaert
committed
use Drupal\Component\Utility\UrlHelper;

Angie Byron
committed
use Drupal\Component\Utility\Xss;
use Drupal\Core\Database\Database;

Dries Buytaert
committed
use Drupal\Core\Form\FormStateInterface;

Alex Pott
committed
use Drupal\Core\Form\OptGroup;

Angie Byron
committed
use Drupal\Core\Render\Element;
use Drupal\Core\Template\Attribute;
use Symfony\Component\HttpFoundation\RedirectResponse;

Angie Byron
committed
/**

Dries Buytaert
committed
* Fetches a form from the cache.

Alex Pott
committed
*
* @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
* Use \Drupal::formBuilder()->getCache().
*

Angie Byron
committed
* @see \Drupal\Core\Form\FormCacheInterface::getCache().
*/

Dries Buytaert
committed
function form_get_cache($form_build_id, FormStateInterface $form_state) {

Alex Pott
committed
return \Drupal::formBuilder()->getCache($form_build_id, $form_state);
}
/**

Dries Buytaert
committed
* Stores a form in the cache.

Alex Pott
committed
*
* @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
* Use \Drupal::formBuilder()->setCache().
*

Angie Byron
committed
* @see \Drupal\Core\Form\FormCacheInterface::setCache().
*/

Alex Pott
committed
function form_set_cache($form_build_id, $form, FormStateInterface $form_state) {

Alex Pott
committed
\Drupal::formBuilder()->setCache($form_build_id, $form, $form_state);

Dries Buytaert
committed
}
/**

Dries Buytaert
committed
* Retrieves, populates, and processes a form.
*
* @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
* Use \Drupal::formBuilder()->submitForm().
*
* @see \Drupal\Core\Form\FormBuilderInterface::submitForm().
*/

Dries Buytaert
committed
function drupal_form_submit($form_arg, FormStateInterface $form_state) {

Alex Pott
committed
\Drupal::formBuilder()->submitForm($form_arg, $form_state);
}

Dries Buytaert
committed
/**

Dries Buytaert
committed
* Processes a form submission.
*
* @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
* Use \Drupal::formBuilder()->processForm().
*
* @see \Drupal\Core\Form\FormBuilderInterface::processForm().

Dries Buytaert
committed
function drupal_process_form($form_id, &$form, FormStateInterface $form_state) {

Alex Pott
committed
\Drupal::formBuilder()->processForm($form_id, $form, $form_state);

Dries Buytaert
committed
}

Dries Buytaert
committed
/**

Dries Buytaert
committed
* Executes custom validation and submission handlers for a given form.
*
* @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
* Use either \Drupal::service('form_submitter')->executeSubmitHandlers() or

Alex Pott
committed
* \Drupal::service('form_validator')->executeValidateHandlers().
*
* @see \Drupal\Core\Form\FormSubmitterInterface::executeSubmitHandlers()

Alex Pott
committed
* @see \Drupal\Core\Form\FormValidatorInterface::executeValidateHandlers()

Dries Buytaert
committed
*/

Dries Buytaert
committed
function form_execute_handlers($type, &$form, FormStateInterface $form_state) {

Alex Pott
committed
if ($type == 'submit') {
\Drupal::service('form_submitter')->executeSubmitHandlers($form, $form_state);

Alex Pott
committed
}
elseif ($type == 'validate') {
\Drupal::service('form_validator')->executeValidateHandlers($form, $form_state);
}

Dries Buytaert
committed
}

Dries Buytaert
committed
* Flags an element as having an error.

Alex Pott
committed
*
* @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
* Use $form_state->setError().
*
* @see \Drupal\Core\Form\FormStateInterface::setError().

Dries Buytaert
committed
function form_error(&$element, FormStateInterface $form_state, $message = '') {
$form_state->setError($element, $message);

Dries Buytaert
committed
* Builds and processes all elements in the structured form array.
*
* @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
* Use \Drupal::formBuilder()->doBuildForm().
*
* @see \Drupal\Core\Form\FormBuilderInterface::doBuildForm().

Dries Buytaert
committed
function form_builder($form_id, &$element, FormStateInterface $form_state) {

Alex Pott
committed
return \Drupal::formBuilder()->doBuildForm($form_id, $element, $form_state);
}
/**
* Removes internal Form API elements and buttons from submitted form values.
*
* This function can be used when a module wants to store all submitted form
* values, for example, by serializing them into a single database column. In
* such cases, all internal Form API values and all form button elements should
* not be contained, and this function allows to remove them before the module
* proceeds to storage. Next to button elements, the following internal values
* are removed:
* - form_id
* - form_token
* - form_build_id
* - op
*
* @param $form_state

Dries Buytaert
committed
* The current state of the form, including submitted form values.
*/

Dries Buytaert
committed
function form_state_values_clean(FormStateInterface $form_state) {
// Remove internal Form API values.

Alex Pott
committed
$form_state
->unsetValue('form_id')
->unsetValue('form_token')
->unsetValue('form_build_id')
->unsetValue('op');
// Remove button values.
// form_builder() collects all button elements in a form. We remove the button
// value separately for each button element.
foreach ($form_state['buttons'] as $button) {
// Remove this button's value from the submitted form values by finding
// the value corresponding to this button.
// We iterate over the #parents of this button and move a reference to

Alex Pott
committed
// each parent in $form_state->getValues(). For example, if #parents is:
// array('foo', 'bar', 'baz')

Alex Pott
committed
// then the corresponding $form_state->getValues() part will look like this:
// array(
// 'foo' => array(
// 'bar' => array(
// 'baz' => 'button_value',
// ),
// ),
// )
// We start by (re)moving 'baz' to $last_parent, so we are able unset it
// at the end of the iteration. Initially, $values will contain a

Alex Pott
committed
// reference to $form_state->getValues(), but in the iteration we move the
// reference to $form_state->getValue('foo'), and finally to
// $form_state->getValue(array('foo', 'bar')), which is the level where we
// can unset 'baz' (that is stored in $last_parent).
$parents = $button['#parents'];
$last_parent = array_pop($parents);
$key_exists = NULL;

Alex Pott
committed
$values = &NestedArray::getValue($form_state->getValues(), $parents, $key_exists);
if ($key_exists && is_array($values)) {
unset($values[$last_parent]);
}
}
}

Dries Buytaert
committed
* Changes submitted form values during form validation.
* @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.

Angie Byron
committed
* Use $form_state->setValueForElement().
*

Angie Byron
committed
* @see \Drupal\Core\Form\FormStateInterface::setValueForElement().

Dries Buytaert
committed
function form_set_value($element, $value, FormStateInterface $form_state) {

Angie Byron
committed
$form_state->setValueForElement($element, $value);
/**
* Allows PHP array processing of multiple select options with the same value.
*
* Used for form select elements which need to validate HTML option groups
* and multiple options which may return the same value. Associative PHP arrays
* cannot handle these structures, since they share a common key.
*
* @param $array
* The form options array to process.
*
* @return
* An array with all hierarchical elements flattened to a single array.
*
* @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.

Alex Pott
committed
* Use \Drupal\Core\Form\OptGroup::flattenOptions().

Angie Byron
committed
function form_options_flatten($array) {

Alex Pott
committed
return OptGroup::flattenOptions($array);
* Prepares variables for select element templates.
*
* Default template: select.html.twig.
*
* It is possible to group options together; to do this, change the format of
* $options to an associative array in which the keys are group labels, and the
* values are associative arrays in the normal $options format.

Dries Buytaert
committed
* @param $variables
* An associative array containing:
* - element: An associative array containing the properties of the element.
* Properties used: #title, #value, #options, #description, #extra,
* #multiple, #required, #name, #attributes, #size.
function template_preprocess_select(&$variables) {

Dries Buytaert
committed
$element = $variables['element'];

Angie Byron
committed
Element::setAttributes($element, array('id', 'name', 'size'));

Angie Byron
committed
Element\RenderElement::setAttributes($element, array('form-select'));

Dries Buytaert
committed
$variables['attributes'] = $element['#attributes'];
$variables['options'] = form_select_options($element);

Dries Buytaert
committed
}

Angie Byron
committed
/**

Dries Buytaert
committed
* Converts a select form element's options array into HTML.

Angie Byron
committed
*
* @param $element
* An associative array containing the properties of the element.
* @param $choices
* Mixed: Either an associative array of items to list as choices, or an
* object with an 'option' member that is an associative array. This
* parameter is only used internally and should not be passed.

Dries Buytaert
committed
*

Angie Byron
committed
* @return
* An HTML string of options for the select form element.
*/

Dries Buytaert
committed
function form_select_options($element, $choices = NULL) {
if (!isset($choices)) {
if (empty($element['#options'])) {
return '';
}

Dries Buytaert
committed
$choices = $element['#options'];
}
// array_key_exists() accommodates the rare event where $element['#value'] is NULL.
// isset() fails in this situation.
$value_valid = isset($element['#value']) || array_key_exists('#value', $element);

Dries Buytaert
committed
$value_is_array = $value_valid && is_array($element['#value']);
// Check if the element is multiple select and no value has been selected.
$empty_value = (empty($element['#value']) && !empty($element['#multiple']));

Dries Buytaert
committed
$options = '';
foreach ($choices as $key => $choice) {

Alex Pott
committed
$options .= '<optgroup label="' . String::checkPlain($key) . '">';

Dries Buytaert
committed
$options .= form_select_options($element, $choice);
$options .= '</optgroup>';
elseif (is_object($choice) && isset($choice->option)) {

Neil Drumm
committed
$options .= form_select_options($element, $choice->option);
}
$key = (string) $key;
$empty_choice = $empty_value && $key == '_none';
if ($value_valid && ((!$value_is_array && (string) $element['#value'] === $key || ($value_is_array && in_array($key, $element['#value']))) || $empty_choice)) {
$selected = ' selected="selected"';
}
else {
$selected = '';
}

Angie Byron
committed
$options .= '<option value="' . String::checkPlain($key) . '"' . $selected . '>' . String::checkPlain($choice) . '</option>';
return SafeMarkup::set($options);

Steven Wittens
committed
/**

Dries Buytaert
committed
* Returns the indexes of a select element's options matching a given key.
*
* This function is useful if you need to modify the options that are
* already in a form element; for example, to remove choices which are
* not valid because of additional filters imposed by another module.
* One example might be altering the choices in a taxonomy selector.
* To correctly handle the case of a multiple hierarchy taxonomy,
* #options arrays can now hold an array of objects, instead of a
* direct mapping of keys to labels, so that multiple choices in the
* selector can have the same key (and label). This makes it difficult
* to manipulate directly, which is why this helper function exists.
*
* This function does not support optgroups (when the elements of the
* #options array are themselves arrays), and will return FALSE if
* arrays are found. The caller must either flatten/restore or
* manually do their manipulations in this case, since returning the
* index is not sufficient, and supporting this would make the
* "helper" too complicated and cumbersome to be of any help.
*
* As usual with functions that can return array() or FALSE, do not
* forget to use === and !== if needed.

Steven Wittens
committed
*
* @param $element
* The select element to search.

Steven Wittens
committed
* @param $key
* The key to look for.

Dries Buytaert
committed
*

Steven Wittens
committed
* @return
* An array of indexes that match the given $key. Array will be
* empty if no elements were found. FALSE if optgroups were found.

Steven Wittens
committed
*/
function form_get_options($element, $key) {
$keys = array();
foreach ($element['#options'] as $index => $choice) {
if (is_array($choice)) {
return FALSE;
}

Angie Byron
committed
elseif (is_object($choice)) {
if (isset($choice->option[$key])) {
$keys[] = $index;
}
}

Angie Byron
committed
elseif ($index == $key) {
$keys[] = $index;

Steven Wittens
committed
}
}
return $keys;

Steven Wittens
committed
}

Angie Byron
committed
* Prepares variables for fieldset element templates.

Angie Byron
committed
* Default template: fieldset.html.twig.
*
* @param array $variables

Dries Buytaert
committed
* An associative array containing:
* - element: An associative array containing the properties of the element.

Angie Byron
committed
* Properties used: #attributes, #children, #description, #id, #title,
* #value.

Angie Byron
committed
function template_preprocess_fieldset(&$variables) {

Dries Buytaert
committed
$element = $variables['element'];

Angie Byron
committed
Element::setAttributes($element, array('id'));

Angie Byron
committed
Element\RenderElement::setAttributes($element, array('form-wrapper'));

Angie Byron
committed
$variables['attributes'] = $element['#attributes'];
$variables['attributes']['class'][] = 'form-item';
$variables['prefix'] = isset($element['#field_prefix']) ? $element['#field_prefix'] : NULL;
$variables['suffix'] = isset($element['#field_suffix']) ? $element['#field_suffix'] : NULL;
$variables['children'] = $element['#children'];

Dries Buytaert
committed
$legend_attributes = array();
if (isset($element['#title_display']) && $element['#title_display'] == 'invisible') {
$legend_attributes['class'][] = 'visually-hidden';

Dries Buytaert
committed
}

Angie Byron
committed
$variables['legend']['attributes'] = new Attribute($legend_attributes);
$variables['legend']['title'] = (isset($element['#title']) && $element['#title'] !== '') ? Xss::filterAdmin($element['#title']) : '';
$legend_span_attributes = array('class' => array('fieldset-legend'));
if (!empty($element['#required'])) {
$legend_span_attributes['class'][] = 'form-required';
$variables['legend_span']['attributes'] = new Attribute($legend_span_attributes);
}
if (!empty($element['#description'])) {

Angie Byron
committed
$description_id = $element['#attributes']['id'] . '--description';
$description_attributes = array(
'class' => 'description',
'id' => $description_id,
);
$variables['description']['attributes'] = new Attribute($description_attributes);
$variables['description']['content'] = $element['#description'];
// Add the description's id to the fieldset aria attributes.
$variables['attributes']['aria-describedby'] = $description_id;

Angie Byron
committed
}
/**
* Prepares variables for details element templates.
*
* Default template: details.html.twig.
*
* @param array $variables
* An associative array containing:
* - element: An associative array containing the properties of the element.

Angie Byron
committed
* Properties used: #attributes, #children, #open,

Angie Byron
committed
* #description, #id, #title, #value, #optional.
*/
function template_preprocess_details(&$variables) {
$element = $variables['element'];
$variables['attributes'] = $element['#attributes'];
$variables['summary_attributes'] = new Attribute();
if (!empty($element['#title'])) {
$variables['summary_attributes']['role'] = 'button';
if (!empty($element['#attributes']['id'])) {
$variables['summary_attributes']['aria-controls'] = $element['#attributes']['id'];

Angie Byron
committed
$variables['summary_attributes']['aria-expanded'] = !empty($element['#attributes']['open']);
$variables['summary_attributes']['aria-pressed'] = $variables['summary_attributes']['aria-expanded'];
}
$variables['title'] = (!empty($element['#title'])) ? $element['#title'] : '';
$variables['description'] = (!empty($element['#description'])) ? $element['#description'] : '';
$variables['children'] = (isset($element['#children'])) ? $element['#children'] : '';
$variables['value'] = (isset($element['#value'])) ? $element['#value'] : '';
}
* Prepares variables for radios templates.
* Default template: radios.html.twig.
*
* @param array $variables

Dries Buytaert
committed
* An associative array containing:
* - element: An associative array containing the properties of the element.
* Properties used: #title, #value, #options, #description, #required,
* #attributes, #children.
function template_preprocess_radios(&$variables) {

Dries Buytaert
committed
$element = $variables['element'];
$variables['attributes'] = array();

Dries Buytaert
committed
if (isset($element['#id'])) {
$variables['attributes']['id'] = $element['#id'];

Dries Buytaert
committed
}
$variables['attributes']['class'][] = 'form-radios';
if (!empty($element['#attributes']['class'])) {
$variables['attributes']['class'] = array_merge($variables['attributes']['class'], $element['#attributes']['class']);
if (isset($element['#attributes']['title'])) {
$variables['attributes']['title'] = $element['#attributes']['title'];
$variables['children'] = $element['#children'];
}
* Prepares variables for checkboxes templates.
* Default template: checkboxes.html.twig.
*
* @param array $variables

Dries Buytaert
committed
* An associative array containing:
* - element: An associative array containing the properties of the element.
* Properties used: #children, #attributes.
function template_preprocess_checkboxes(&$variables) {

Dries Buytaert
committed
$element = $variables['element'];
$variables['attributes'] = array();

Dries Buytaert
committed
if (isset($element['#id'])) {
$variables['attributes']['id'] = $element['#id'];

Dries Buytaert
committed
}
$variables['attributes']['class'] = array();
$variables['attributes']['class'][] = 'form-checkboxes';
if (!empty($element['#attributes']['class'])) {
$variables['attributes']['class'] = array_merge($variables['attributes']['class'], $element['#attributes']['class']);
if (isset($element['#attributes']['title'])) {
$variables['attributes']['title'] = $element['#attributes']['title'];
$variables['children'] = $element['#children'];
}
/**

Angie Byron
committed
* Prepares variables for vertical tabs templates.
*
* Default template: vertical-tabs.html.twig.
*
* @param array $variables
* An associative array containing:
* - element: An associative array containing the properties and children of
* the details element. Properties used: #children.

Angie Byron
committed
function template_preprocess_vertical_tabs(&$variables) {
$element = $variables['element'];
$variables['children'] = (!empty($element['#children'])) ? $element['#children'] : '';

Angie Byron
committed
* Prepares variables for input templates.
*
* Default template: input.html.twig.
*
* @param array $variables
* An associative array containing:
* - element: An associative array containing the properties of the element.
* Properties used: #attributes.

Angie Byron
committed
function template_preprocess_input(&$variables) {
$element = $variables['element'];
$variables['children'] = $element['#children'];

Dries Buytaert
committed
/**

Angie Byron
committed
* Prepares variables for form templates.
*
* Default template: form.html.twig.
*
* @param $variables
* An associative array containing:
* - element: An associative array containing the properties of the element.
* Properties used: #action, #method, #attributes, #children

Dries Buytaert
committed
*/

Angie Byron
committed
function template_preprocess_form(&$variables) {
$element = $variables['element'];
if (isset($element['#action'])) {
$element['#attributes']['action'] = UrlHelper::stripDangerousProtocols($element['#action']);

Dries Buytaert
committed
}

Angie Byron
committed
Element::setAttributes($element, array('method', 'id'));
if (empty($element['#attributes']['accept-charset'])) {
$element['#attributes']['accept-charset'] = "UTF-8";

Dries Buytaert
committed
}

Angie Byron
committed
$variables['attributes'] = $element['#attributes'];
$variables['children'] = $element['#children'];

Dries Buytaert
committed
}

Dries Buytaert
committed
/**

Angie Byron
committed
* Prepares variables for textarea templates.
*
* Default template: textarea.html.twig.
*
* @param array $variables
* An associative array containing:
* - element: An associative array containing the properties of the element.
* Properties used: #title, #value, #description, #rows, #cols,
* #placeholder, #required, #attributes, #resizable
*

Dries Buytaert
committed
*/

Angie Byron
committed
function template_preprocess_textarea(&$variables) {
$element = $variables['element'];
Element::setAttributes($element, array('id', 'name', 'rows', 'cols', 'placeholder'));
Element\RenderElement::setAttributes($element, array('form-textarea'));
$variables['wrapper_attributes'] = new Attribute(array(
'class' => array('form-textarea-wrapper'),
));

Dries Buytaert
committed

Angie Byron
committed
// Add resizable behavior.
if (!empty($element['#resizable'])) {
$element['#attributes']['class'][] = 'resize-' . $element['#resizable'];

Angie Byron
committed
$variables['attributes'] = new Attribute($element['#attributes']);
$variables['value'] = String::checkPlain($element['#value']);
* Returns HTML for a form element.
* Prepares variables for form element templates.
*
* Default template: form-element.html.twig.

Dries Buytaert
committed
* Each form element is wrapped in a DIV container having the following CSS
* classes:
* - form-item: Generic for all form elements.
* - form-type-#type: The internal element #type.
* - form-item-#name: The internal form element #name (usually derived from the
* $form structure and set via form_builder()).
* - form-disabled: Only set if the form element is #disabled.
*
* In addition to the element itself, the DIV contains a label for the element
* based on the optional #title_display property, and an optional #description.

Dries Buytaert
committed
*
* The optional #title_display property can have these values:
* - before: The label is output before the element. This is the default.
* The label includes the #title and the required marker, if #required.
* - after: The label is output after the element. For example, this is used
* for radio and checkbox #type elements as set in system_element_info().
* If the #title is empty but the field is #required, the label will
* contain only the required marker.
* - invisible: Labels are critical for screen readers to enable them to
* properly navigate through forms but can be visually distracting. This
* property hides the label for everyone except screen readers.

Dries Buytaert
committed
* - attribute: Set the title attribute on the element to create a tooltip
* but output no label element. This is supported only for checkboxes

Angie Byron
committed
* and radios in
* \Drupal\Core\Render\Element\CompositeFormElementTrait::preRenderCompositeFormElement().
* It is used where a visual label is not needed, such as a table of
* checkboxes where the row and column provide the context. The tooltip will
* include the title and required marker.

Dries Buytaert
committed
*
* If the #title property is not set, then the label and any required marker
* will not be output, regardless of the #title_display or #required values.
* This can be useful in cases such as the password_confirm element, which
* creates children elements that have their own labels and required markers,
* but the parent element should have neither. Use this carefully because a
* field without an associated label can cause accessibility challenges.
*
* @param array $variables

Dries Buytaert
committed
* An associative array containing:
* - element: An associative array containing the properties of the element.

Dries Buytaert
committed
* Properties used: #title, #title_display, #description, #id, #required,
* #children, #type, #name.
function template_preprocess_form_element(&$variables) {

Dries Buytaert
committed
$element = &$variables['element'];

Dries Buytaert
committed

Dries Buytaert
committed
// This function is invoked as theme wrapper, but the rendered form element
// may not necessarily have been processed by form_builder().
$element += array(
'#title_display' => 'before',
);

Dries Buytaert
committed
// Take over any #wrapper_attributes defined by the element.
// @todo Temporary hack for #type 'item'.
// @see http://drupal.org/node/1829202
$variables['attributes'] = array();

Dries Buytaert
committed
if (isset($element['#wrapper_attributes'])) {
$variables['attributes'] = $element['#wrapper_attributes'];

Dries Buytaert
committed
}
// Add element #id for #type 'item'.
if (isset($element['#markup']) && !empty($element['#id'])) {
$variables['attributes']['id'] = $element['#id'];

Dries Buytaert
committed
// Add element's #type and #name as class to aid with JS/CSS selectors.
$variables['attributes']['class'][] = 'form-item';

Dries Buytaert
committed
if (!empty($element['#type'])) {
$variables['attributes']['class'][] = 'form-type-' . strtr($element['#type'], '_', '-');

Dries Buytaert
committed
}
if (!empty($element['#name'])) {
$variables['attributes']['class'][] = 'form-item-' . strtr($element['#name'], array(' ' => '-', '_' => '-', '[' => '-', ']' => ''));

Dries Buytaert
committed
}

Dries Buytaert
committed
// Add a class for disabled elements to facilitate cross-browser styling.
if (!empty($element['#attributes']['disabled'])) {
$variables['attributes']['class'][] = 'form-disabled';

Dries Buytaert
committed
}

Dries Buytaert
committed
// If #title is not set, we don't display any label.

Dries Buytaert
committed
if (!isset($element['#title'])) {
$element['#title_display'] = 'none';
}
// If #title_dislay is not some of the visible options, add a CSS class.
if ($element['#title_display'] != 'before' && $element['#title_display'] != 'after') {
$variables['attributes']['class'][] = 'form-no-label';
}
$variables['prefix'] = isset($element['#field_prefix']) ? $element['#field_prefix'] : NULL;
$variables['suffix'] = isset($element['#field_suffix']) ? $element['#field_suffix'] : NULL;
$variables['description'] = NULL;
if (!empty($element['#description'])) {

Alex Pott
committed
$variables['description_display'] = $element['#description_display'];
$description_attributes = array('class' => array('description'));
if ($element['#description_display'] === 'invisible') {
$description_attributes['class'][] = 'visually-hidden';
}
if (!empty($element['#id'])) {
$description_attributes['id'] = $element['#id'] . '--description';
$variables['description']['attributes'] = new Attribute($description_attributes);
$variables['description']['content'] = $element['#description'];
// Add label_display and label variables to template.
$variables['label_display'] = $element['#title_display'];
$variables['label'] = array('#theme' => 'form_element_label');
$variables['label'] += array_intersect_key($element, array_flip(array('#id', '#required', '#title', '#title_display')));
$variables['children'] = $element['#children'];

Dries Buytaert
committed

Dries Buytaert
committed
/**
* Prepares variables for form label templates.

Dries Buytaert
committed
*
* Form element labels include the #title and a #required marker. The label is
* associated with the element itself by the element #id. Labels may appear

Dries Buytaert
committed
* before or after elements, depending on theme_form_element() and
* #title_display.

Dries Buytaert
committed
*
* This function will not be called for elements with no labels, depending on
* #title_display. For elements that have an empty #title and are not required,
* this function will output no label (''). For required elements that have an
* empty #title, this will output the required marker alone within the label.
* The label will use the #id to associate the marker with the field that is
* required. That is especially important for screenreader users to know
* which field is required.
*
* @param array $variables

Dries Buytaert
committed
* An associative array containing:
* - element: An associative array containing the properties of the element.
* Properties used: #required, #title, #id, #value, #description.
*/
function template_preprocess_form_element_label(&$variables) {

Dries Buytaert
committed
$element = $variables['element'];
// If title and required marker are both empty, output no label.
$variables['title'] = (isset($element['#title']) && $element['#title'] !== '') ? Xss::filterAdmin($element['#title']) : '';
$variables['attributes'] = array();
// Style the label as class option to display inline with the element.

Dries Buytaert
committed
if ($element['#title_display'] == 'after') {
$variables['attributes']['class'][] = 'option';

Dries Buytaert
committed
}
// Show label only to screen readers to avoid disruption in visual flows.
elseif ($element['#title_display'] == 'invisible') {
$variables['attributes']['class'][] = 'visually-hidden';
}

Dries Buytaert
committed
// A #for property of a dedicated #type 'label' element as precedence.
if (!empty($element['#for'])) {
$variables['attributes']['for'] = $element['#for'];

Dries Buytaert
committed
// A custom #id allows the referenced form input element to refer back to
// the label element; e.g., in the 'aria-labelledby' attribute.
if (!empty($element['#id'])) {
$variables['attributes']['id'] = $element['#id'];

Dries Buytaert
committed
}
}
// Otherwise, point to the #id of the form input element.
elseif (!empty($element['#id'])) {
$variables['attributes']['for'] = $element['#id'];

Dries Buytaert
committed
}
// For required elements a 'form-required' class is appended to the
// label attributes.
$variables['required'] = FALSE;
if (!empty($element['#required'])) {
$variables['required'] = TRUE;
$variables['attributes']['class'][] = 'form-required';

Dries Buytaert
committed
}
/**
* @defgroup batch Batch operations
* @{

Dries Buytaert
committed
* Creates and processes batch operations.
*
* Functions allowing forms processing to be spread out over several page
* requests, thus ensuring that the processing does not get interrupted
* because of a PHP timeout, while allowing the user to receive feedback
* on the progress of the ongoing operations.
*
* The API is primarily designed to integrate nicely with the Form API

Dries Buytaert
committed
* workflow, but can also be used by non-Form API scripts (like update.php)
* or even simple page callbacks (which should probably be used sparingly).
*
* Example:
* @code
* $batch = array(
* 'title' => t('Exporting'),
* 'operations' => array(

Dries Buytaert
committed
* array('my_function_1', array($account->id(), 'story')),
* array('my_function_2', array()),
* ),
* 'finished' => 'my_finished_callback',

Angie Byron
committed
* 'file' => 'path_to_file_containing_myfunctions',
* );
* batch_set($batch);

Angie Byron
committed
* // Only needed if not inside a form _submit handler.
* // Setting redirect in batch_process.
* batch_process('node/1');
* @endcode
*

Angie Byron
committed
* Note: if the batch 'title', 'init_message', 'progress_message', or
* 'error_message' could contain any user input, it is the responsibility of
* the code calling batch_set() to sanitize them first with a function like

Angie Byron
committed
* \Drupal\Component\Utility\String::checkPlain() or
* \Drupal\Component\Utility\Xss::filter(). Furthermore, if the batch operation
* returns any user input in the 'results' or 'message' keys of $context, it
* must also sanitize them first.

Jennifer Hodgdon
committed
* Sample callback_batch_operation():
* @code
* // Simple and artificial: load a node of a given type for a given user
* function my_function_1($uid, $type, &$context) {
* // The $context array gathers batch context information about the execution (read),
* // as well as 'return values' for the current operation (write)
* // The following keys are provided :
* // 'results' (read / write): The array of results gathered so far by
* // the batch processing, for the current operation to append its own.
* // 'message' (write): A text message displayed in the progress page.
* // The following keys allow for multi-step operations :
* // 'sandbox' (read / write): An array that can be freely used to
* // store persistent data between iterations. It is recommended to
* // use this instead of $_SESSION, which is unsafe if the user
* // continues browsing in a separate window while the batch is processing.
* // 'finished' (write): A float number between 0 and 1 informing
* // the processing engine of the completion level for the operation.

Gábor Hojtsy
committed
* // 1 (or no value explicitly set) means the operation is finished
* // and the batch processing can continue to the next operation.
*
* $nodes = entity_load_multiple_by_properties('node', array('uid' => $uid, 'type' => $type));
* $node = reset($nodes);

Angie Byron
committed
* $context['results'][] = $node->id() . ' : ' . String::checkPlain($node->label());
* $context['message'] = String::checkPlain($node->label());
* }
*
* // A more advanced example is a multi-step operation that loads all rows,
* // five by five.
* function my_function_2(&$context) {
* if (empty($context['sandbox'])) {
* $context['sandbox']['progress'] = 0;
* $context['sandbox']['current_id'] = 0;
* $context['sandbox']['max'] = db_query('SELECT COUNT(DISTINCT id) FROM {example}')->fetchField();
* }
* $limit = 5;
* $result = db_select('example')
* ->fields('example', array('id'))
* ->condition('id', $context['sandbox']['current_id'], '>')
* ->orderBy('id')
* ->range(0, $limit)
* ->execute();
* foreach ($result as $row) {
* $context['results'][] = $row->id . ' : ' . String::checkPlain($row->title);
* $context['sandbox']['progress']++;
* $context['sandbox']['current_id'] = $row->id;
* $context['message'] = String::checkPlain($row->title);
* }
* if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
* $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
* }
* }
* @endcode
*

Jennifer Hodgdon
committed
* Sample callback_batch_finished():
* @code
* function batch_test_finished($success, $results, $operations) {

Angie Byron
committed
* // The 'success' parameter means no fatal PHP errors were detected. All
* // other error management should be handled using 'results'.
* if ($success) {

Gábor Hojtsy
committed
* $message = format_plural(count($results), 'One post processed.', '@count posts processed.');
* }
* else {
* $message = t('Finished with an error.');
* }
* drupal_set_message($message);
* // Providing data for the redirected page is done through $_SESSION.
* foreach ($results as $result) {
* $items[] = t('Loaded node %title.', array('%title' => $result));
* }

Dries Buytaert
committed
* $_SESSION['my_batch_results'] = $items;
* }
* @endcode
*/
/**
* Adds a new batch.
*
* Batch operations are added as new batch sets. Batch sets are used to spread
* processing (primarily, but not exclusively, forms processing) over several
* page requests. This helps to ensure that the processing is not interrupted
* due to PHP timeouts, while users are still able to receive feedback on the
* progress of the ongoing operations. Combining related operations into
* distinct batch sets provides clean code independence for each batch set,
* ensuring that two or more batches, submitted independently, can be processed
* without mutual interference. Each batch set may specify its own set of
* operations and results, produce its own UI messages, and trigger its own
* 'finished' callback. Batch sets are processed sequentially, with the progress
* bar starting afresh for each new set.
*
* @param $batch_definition
* An associative array defining the batch, with the following elements (all
* are optional except as noted):

Jennifer Hodgdon
committed
* - operations: (required) Array of operations to be performed, where each
* item is an array consisting of the name of an implementation of
* callback_batch_operation() and an array of parameter.

Angie Byron
committed
* Example:
* @code
* array(

Jennifer Hodgdon
committed
* array('callback_batch_operation_1', array($arg1)),
* array('callback_batch_operation_2', array($arg2_1, $arg2_2)),

Angie Byron
committed
* )
* @endcode
* - title: A safe, translated string to use as the title for the progress
* page. Defaults to t('Processing').
* - init_message: Message displayed while the processing is initialized.

Angie Byron
committed
* Defaults to t('Initializing.').
* - progress_message: Message displayed while processing the batch. Available
* placeholders are @current, @remaining, @total, @percentage, @estimate and
* @elapsed. Defaults to t('Completed @current of @total.').
* - error_message: Message displayed if an error occurred while processing

Angie Byron
committed
* the batch. Defaults to t('An error has occurred.').

Jennifer Hodgdon
committed
* - finished: Name of an implementation of callback_batch_finished(). This is
* executed after the batch has completed. This should be used to perform
* any result massaging that may be needed, and possibly save data in
* $_SESSION for display after final page redirection.
* - file: Path to the file containing the definitions of the 'operations' and
* 'finished' functions, for instance if they don't reside in the main
* .module file. The path should be relative to base_path(), and thus should
* be built using drupal_get_path().
* - css: Array of paths to CSS files to be used on the progress page.
* - url_options: options passed to url() when constructing redirect URLs for
* the batch.
* - safe_strings: Internal use only. Used to store and retrieve strings
* marked as safe between requests.
*/
function batch_set($batch_definition) {
if ($batch_definition) {
$batch =& batch_get();
// Initialize the batch if needed.
if (empty($batch)) {
$batch = array(
'sets' => array(),
'has_form_submits' => FALSE,
);
}
// Base and default properties for the batch set.
$init = array(
'sandbox' => array(),
'results' => array(),
'success' => FALSE,
'start' => 0,

Dries Buytaert
committed
'elapsed' => 0,
);
$defaults = array(
'title' => t('Processing'),
'init_message' => t('Initializing.'),
'progress_message' => t('Completed @current of @total.'),
'error_message' => t('An error has occurred.'),

Dries Buytaert
committed
'css' => array(),
);
$batch_set = $init + $batch_definition + $defaults;
// Tweak init_message to avoid the bottom of the page flickering down after
// init phase.
$batch_set['init_message'] .= '<br/> ';
// The non-concurrent workflow of batch execution allows us to save
// numberOfItems() queries by handling our own counter.
$batch_set['total'] = count($batch_set['operations']);
$batch_set['count'] = $batch_set['total'];
// Add the set to the batch.
if (empty($batch['id'])) {
// The batch is not running yet. Simply add the new set.
$batch['sets'][] = $batch_set;
}
else {
// The set is being added while the batch is running. Insert the new set
// right after the current one to ensure execution order, and store its
// operations in a queue.
$index = $batch['current_set'] + 1;
$slice1 = array_slice($batch['sets'], 0, $index);
$slice2 = array_slice($batch['sets'], $index);
$batch['sets'] = array_merge($slice1, array($batch_set), $slice2);
_batch_populate_queue($batch, $index);
}
}
}
/**

Angie Byron
committed
* Processes the batch.

Dries Buytaert
committed
*
* This function is generally not needed in form submit handlers;
* Form API takes care of batches that were set during form submission.
*
* @param $redirect
* (optional) Path to redirect to when the batch has finished processing.
* @param $url
* (optional - should only be used for separate scripts like update.php)
* URL of the batch processing page.

Dries Buytaert
committed
* @param $redirect_callback
* (optional) Specify a function to be called to redirect to the progressive
* processing page.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse|null
* A redirect response if the batch is progressive. No return value otherwise.
*/
function batch_process($redirect = NULL, $url = 'batch', $redirect_callback = NULL) {
$batch =& batch_get();
if (isset($batch)) {
// Add process information
$process_info = array(
'current_set' => 0,
'progressive' => TRUE,

Dries Buytaert
committed
'url' => $url,

Angie Byron
committed
'url_options' => array(),

Dries Buytaert
committed
'source_url' => current_path(),
'batch_redirect' => $redirect,
'theme' => \Drupal::theme()->getActiveTheme()->getName(),

Dries Buytaert
committed
'redirect_callback' => $redirect_callback,
);
$batch += $process_info;
// The batch is now completely built. Allow other modules to make changes
// to the batch so that it is easier to reuse batch processes in other
// environments.
\Drupal::moduleHandler()->alter('batch', $batch);

Dries Buytaert
committed
// Assign an arbitrary id: don't rely on a serial column in the 'batch'
// table, since non-progressive batches skip database storage completely.
$batch['id'] = db_next_id();
// Move operations to a job queue. Non-progressive batches will use a
// memory-based queue.
foreach ($batch['sets'] as $key => $batch_set) {
_batch_populate_queue($batch, $key);
}
// Initiate processing.
if ($batch['progressive']) {
// Now that we have a batch id, we can generate the redirection link in
// the generic error message.
$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')))));
// Clear the way for the redirection to the batch processing page, by
// saving and unsetting the 'destination', if there is any.
$request = \Drupal::request();
if ($request->query->has('destination')) {
$batch['destination'] = $request->query->get('destination');
$request->query->remove('destination');
}