diff --git a/core/includes/ajax.inc b/core/includes/ajax.inc index 54de9e8..55b4a43 100644 --- a/core/includes/ajax.inc +++ b/core/includes/ajax.inc @@ -5,7 +5,7 @@ * Functions for use with Drupal's Ajax framework. */ -use Drupal\Core\Render\Element\Textfield; +use Drupal\Core\Render\Element; /** * @defgroup ajax Ajax framework @@ -230,16 +230,10 @@ /** * Form element processing handler for the #ajax form property. * - * @param $element - * An associative array containing the properties of the element. - * - * @return - * The processed element. - * - * @see ajax_pre_render_element() + * @deprecated Use \Drupal\Core\Render\Element\FormElement::processAjaxForm(). */ -function ajax_process_form($element, &$form_state) { - return Textfield::processAjaxForm($element, $form_state); +function ajax_process_form(&$element, &$form_state, &$complete_form) { + return Element\FormElement::processAjaxForm($element, $form_state, $complete_form); } /** diff --git a/core/includes/common.inc b/core/includes/common.inc index da65525..16468b3 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -2702,70 +2702,6 @@ function drupal_pre_render_html_tag($element) { } /** - * Pre-render callback: Renders a link into #markup. - * - * Doing so during pre_render gives modules a chance to alter the link parts. - * - * @param $elements - * A structured array whose keys form the arguments to l(): - * - #title: The link text to pass as argument to l(). - * - One of the following - * - #route_name and (optionally) and a #route_parameters array; The route - * name and route parameters which will be passed into the link generator. - * - #href: The system path or URL to pass as argument to l(). - * - #options: (optional) An array of options to pass to l() or the link - * generator. - * - * @return - * The passed-in elements containing a rendered link in '#markup'. - */ -function drupal_pre_render_link($element) { - // By default, link options to pass to l() are normally set in #options. - $element += array('#options' => array()); - // However, within the scope of renderable elements, #attributes is a valid - // way to specify attributes, too. Take them into account, but do not override - // attributes from #options. - if (isset($element['#attributes'])) { - $element['#options'] += array('attributes' => array()); - $element['#options']['attributes'] += $element['#attributes']; - } - - // This #pre_render callback can be invoked from inside or outside of a Form - // API context, and depending on that, a HTML ID may be already set in - // different locations. #options should have precedence over Form API's #id. - // #attributes have been taken over into #options above already. - if (isset($element['#options']['attributes']['id'])) { - $element['#id'] = $element['#options']['attributes']['id']; - } - elseif (isset($element['#id'])) { - $element['#options']['attributes']['id'] = $element['#id']; - } - - // Conditionally invoke ajax_pre_render_element(), if #ajax is set. - if (isset($element['#ajax']) && !isset($element['#ajax_processed'])) { - // If no HTML ID was found above, automatically create one. - if (!isset($element['#id'])) { - $element['#id'] = $element['#options']['attributes']['id'] = drupal_html_id('ajax-link'); - } - // If #ajax['path] was not specified, use the href as Ajax request URL. - if (!isset($element['#ajax']['path'])) { - $element['#ajax']['path'] = $element['#href']; - $element['#ajax']['options'] = $element['#options']; - } - $element = ajax_pre_render_element($element); - } - - if (isset($element['#route_name'])) { - $element['#route_parameters'] = empty($element['#route_parameters']) ? array() : $element['#route_parameters']; - $element['#markup'] = \Drupal::linkGenerator()->generate($element['#title'], $element['#route_name'], $element['#route_parameters'], $element['#options']); - } - else { - $element['#markup'] = l($element['#title'], $element['#href'], $element['#options']); - } - return $element; -} - -/** * Pre-render callback: Collects child links into a single array. * * This function can be added as a pre_render callback for a renderable array, diff --git a/core/includes/form.inc b/core/includes/form.inc index 280d232..9e0b7c7 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -14,7 +14,6 @@ use Drupal\Core\Database\Database; use Drupal\Core\Form\OptGroup; use Drupal\Core\Render\Element; -use Drupal\Core\Render\Element\Textfield; use Drupal\Core\Template\Attribute; use Drupal\Core\Utility\Color; use Symfony\Component\HttpFoundation\RedirectResponse; @@ -1428,19 +1427,10 @@ function form_pre_render_actions_dropbutton(array $element) { /** * #process callback for #pattern form element property. * - * @param $element - * An associative array containing the properties and children of the - * generic input element. - * @param $form_state - * The $form_state array for the form this element belongs to. - * - * @return - * The processed element. - * - * @see form_validate_pattern() + * @deprecated Use \Drupal\Core\Render\Element\FormElement::processPattern(). */ -function form_process_pattern($element, &$form_state) { - return Textfield::processPattern($element, $form_state); +function form_process_pattern(&$element, &$form_state, &$complete_form) { + return Element\FormElement::processPattern($element, $form_state, $complete_form); } /** @@ -1825,167 +1815,28 @@ function form_validate_table($element, &$form_state) { /** * Processes a machine-readable name form element. * - * @param $element - * The form element to process. Properties used: - * - #machine_name: An associative array containing: - * - exists: A callable to invoke for checking whether a submitted machine - * name value already exists. The submitted value is passed as an - * argument. In most cases, an existing API or menu argument loader - * function can be re-used. The callback is only invoked, if the submitted - * value differs from the element's #default_value. - * - source: (optional) The #array_parents of the form element containing - * the human-readable name (i.e., as contained in the $form structure) to - * use as source for the machine name. Defaults to array('label'). - * - label: (optional) A text to display as label for the machine name value - * after the human-readable name form element. Defaults to "Machine name". - * - replace_pattern: (optional) A regular expression (without delimiters) - * matching disallowed characters in the machine name. Defaults to - * '[^a-z0-9_]+'. - * - replace: (optional) A character to replace disallowed characters in the - * machine name via JavaScript. Defaults to '_' (underscore). When using a - * different character, 'replace_pattern' needs to be set accordingly. - * - error: (optional) A custom form error message string to show, if the - * machine name contains disallowed characters. - * - standalone: (optional) Whether the live preview should stay in its own - * form element rather than in the suffix of the source element. Defaults - * to FALSE. - * - #maxlength: (optional) Should be set to the maximum allowed length of the - * machine name. Defaults to 64. - * - #disabled: (optional) Should be set to TRUE in case an existing machine - * name must not be changed after initial creation. + * @deprecated Use \Drupal\Core\Render\Element\MachineName::processMachineName(). */ -function form_process_machine_name($element, &$form_state) { - // We need to pass the langcode to the client. - $language = \Drupal::languageManager()->getCurrentLanguage(); - - // Apply default form element properties. - $element += array( - '#title' => t('Machine-readable name'), - '#description' => t('A unique machine-readable name. Can only contain lowercase letters, numbers, and underscores.'), - '#machine_name' => array(), - '#field_prefix' => '', - '#field_suffix' => '', - '#suffix' => '', - ); - // A form element that only wants to set one #machine_name property (usually - // 'source' only) would leave all other properties undefined, if the defaults - // were defined in hook_element_info(). Therefore, we apply the defaults here. - $element['#machine_name'] += array( - 'source' => array('label'), - 'target' => '#' . $element['#id'], - 'label' => t('Machine name'), - 'replace_pattern' => '[^a-z0-9_]+', - 'replace' => '_', - 'standalone' => FALSE, - 'field_prefix' => $element['#field_prefix'], - 'field_suffix' => $element['#field_suffix'], - ); - - // By default, machine names are restricted to Latin alphanumeric characters. - // So, default to LTR directionality. - if (!isset($element['#attributes'])) { - $element['#attributes'] = array(); - } - $element['#attributes'] += array('dir' => 'ltr'); - - // The source element defaults to array('name'), but may have been overidden. - if (empty($element['#machine_name']['source'])) { - return $element; - } - - // Retrieve the form element containing the human-readable name from the - // complete form in $form_state. By reference, because we may need to append - // a #field_suffix that will hold the live preview. - $key_exists = NULL; - $source = NestedArray::getValue($form_state['complete_form'], $element['#machine_name']['source'], $key_exists); - if (!$key_exists) { - return $element; - } - - $suffix_id = $source['#id'] . '-machine-name-suffix'; - $element['#machine_name']['suffix'] = '#' . $suffix_id; - - if ($element['#machine_name']['standalone']) { - $element['#suffix'] = SafeMarkup::set($element['#suffix'] . '  '); - } - else { - // Append a field suffix to the source form element, which will contain - // the live preview of the machine name. - $source += array('#field_suffix' => ''); - $source['#field_suffix'] = SafeMarkup::set($source['#field_suffix'] . '  '); - - $parents = array_merge($element['#machine_name']['source'], array('#field_suffix')); - NestedArray::setValue($form_state['complete_form'], $parents, $source['#field_suffix']); - } - - $js_settings = array( - 'type' => 'setting', - 'data' => array( - 'machineName' => array( - '#' . $source['#id'] => $element['#machine_name'], - ), - 'langcode' => $language->id, - ), - ); - $element['#attached']['library'][] = 'core/drupal.machine-name'; - $element['#attached']['js'][] = $js_settings; - - return $element; +function form_process_machine_name(&$element, &$form_state, &$complete_form) { + return Element\MachineName::processMachineName($element, $form_state, $complete_form); } /** * Form element validation handler for machine_name elements. * - * Note that #maxlength is validated by _form_validate() already. + * @deprecated Use \Drupal\Core\Render\Element\MachineName::validateMachineName(). */ -function form_validate_machine_name(&$element, &$form_state) { - // Verify that the machine name not only consists of replacement tokens. - if (preg_match('@^' . $element['#machine_name']['replace'] . '+$@', $element['#value'])) { - form_error($element, $form_state, t('The machine-readable name must contain unique characters.')); - } - - // Verify that the machine name contains no disallowed characters. - if (preg_match('@' . $element['#machine_name']['replace_pattern'] . '@', $element['#value'])) { - if (!isset($element['#machine_name']['error'])) { - // Since a hyphen is the most common alternative replacement character, - // a corresponding validation error message is supported here. - if ($element['#machine_name']['replace'] == '-') { - form_error($element, $form_state, t('The machine-readable name must contain only lowercase letters, numbers, and hyphens.')); - } - // Otherwise, we assume the default (underscore). - else { - form_error($element, $form_state, t('The machine-readable name must contain only lowercase letters, numbers, and underscores.')); - } - } - else { - form_error($element, $form_state, $element['#machine_name']['error']); - } - } - - // Verify that the machine name is unique. - if ($element['#default_value'] !== $element['#value']) { - $function = $element['#machine_name']['exists']; - if (call_user_func($function, $element['#value'], $element, $form_state)) { - form_error($element, $form_state, t('The machine-readable name is already in use. It must be unique.')); - } - } +function form_validate_machine_name(&$element, &$form_state, &$complete_form) { + Element\MachineName::validateMachineName($element, $form_state, $complete_form); } /** * Arranges elements into groups. * - * @param $element - * An associative array containing the properties and children of the - * element. Note that $element must be taken by reference here, so processed - * child elements are taken over into $form_state. - * @param $form_state - * The $form_state array for the form this element belongs to. - * - * @return - * The processed element. + * @deprecated Use \Drupal\Core\Render\Element\FormElement::processGroup(). */ -function form_process_group(&$element, &$form_state) { - return Textfield::processGroup($element, $form_state); +function form_process_group(&$element, &$form_state, &$complete_form) { + return Element\FormElement::processGroup($element, $form_state, $complete_form); } /** @@ -2025,15 +1876,10 @@ function form_pre_render_details($element) { /** * Adds members of this group as actual elements for rendering. * - * @param $element - * An associative array containing the properties and children of the - * element. - * - * @return - * The modified element with all group members. + * @deprecated Use \Drupal\Core\Render\ElementElementBase::preRenderGroup(). */ function form_pre_render_group($element) { - return Textfield::preRenderGroup($element); + return Element\RenderElement::preRenderGroup($element); } /** @@ -2121,30 +1967,10 @@ function template_preprocess_vertical_tabs(&$variables) { * Adds autocomplete functionality to elements with a valid * #autocomplete_route_name. * - * Suppose your autocomplete route name is 'mymodule.autocomplete' and its path - * is: '/mymodule/autocomplete/{a}/{b}' - * In your form you have: - * @code - * '#autocomplete_route_name' => 'mymodule.autocomplete', - * '#autocomplete_route_parameters' => array('a' => $some_key, 'b' => $some_id), - * @endcode - * The user types in "keywords" so the full path called is: - * 'mymodule_autocomplete/$some_key/$some_id?q=keywords' - * - * @param array $element - * The form element to process. Properties used: - * - #autocomplete_route_name: A route to be used as callback URL by the - * autocomplete JavaScript library. - * - #autocomplete_route_parameters: The parameters to be used in conjunction - * with the route name. - * @param array $form_state - * An associative array containing the current state of the form. - * - * @return array - * The form element. + * @deprecated Use \Drupal\Core\Render\Element\FormElement::processAutocomplete(). */ -function form_process_autocomplete($element, &$form_state) { - return Textfield::processAutocomplete($element, $form_state); +function form_process_autocomplete(&$element, &$form_state, &$complete_form) { + return Element\FormElement::processAutocomplete($element, $form_state, $complete_form); } /** @@ -2250,16 +2076,10 @@ function form_pre_render_hidden($element) { /** * Prepares a #type 'textfield' render element for theme_input(). * - * @param array $element - * An associative array containing the properties of the element. - * Properties used: #title, #value, #description, #size, #maxlength, - * #placeholder, #required, #attributes. - * - * @return array - * The $element with prepared variables ready for theme_input(). + * @deprecated Use \Drupal\Core\Render\Element\Textfield::preRenderTextfield(). */ function form_pre_render_textfield($element) { - return Textfield::preRenderTextfield($element); + return Element\Textfield::preRenderTextfield($element); } /** diff --git a/core/lib/Drupal/Core/Render/ElementInterface.php b/core/lib/Drupal/Core/Render/Element/ElementInterface.php similarity index 84% rename from core/lib/Drupal/Core/Render/ElementInterface.php rename to core/lib/Drupal/Core/Render/Element/ElementInterface.php index b3ec1ab..7eb753a 100644 --- a/core/lib/Drupal/Core/Render/ElementInterface.php +++ b/core/lib/Drupal/Core/Render/Element/ElementInterface.php @@ -2,10 +2,10 @@ /** * @file - * Contains \Drupal\Core\Render\ElementInterface. + * Contains \Drupal\Core\Render\Element\ElementInterface. */ -namespace Drupal\Core\Render; +namespace Drupal\Core\Render\Element; use Drupal\Component\Plugin\PluginInspectionInterface; diff --git a/core/lib/Drupal/Core/Render/Element/Textfield.php b/core/lib/Drupal/Core/Render/Element/FormElement.php similarity index 53% copy from core/lib/Drupal/Core/Render/Element/Textfield.php copy to core/lib/Drupal/Core/Render/Element/FormElement.php index be9a870..d3ee790 100644 --- a/core/lib/Drupal/Core/Render/Element/Textfield.php +++ b/core/lib/Drupal/Core/Render/Element/FormElement.php @@ -2,61 +2,89 @@ /** * @file - * Contains \Drupal\Core\Render\Element\Textfield. + * Contains \Drupal\Core\Render\Element\FormElement. */ namespace Drupal\Core\Render\Element; -use Drupal\Core\Render\Element; -use Drupal\Core\Render\FormElementBase; - /** - * Formats a single-line text field. - * - * @FormElement("textfield") + * @todo. */ -class Textfield extends FormElementBase { +abstract class FormElement extends RenderElement { /** - * {@inheritdoc} + * Form element processing handler for the #ajax form property. + * + * @param array $element + * An associative array containing the properties of the element. + * @param array $form_state + * An associative array containing the current state of the form. + * @param array $complete_form + * The complete form structure. + * + * @return array + * The processed element. + * + * @see ajax_pre_render_element() */ - public function getInfo() { - $class = get_class($this); - return array( - '#input' => TRUE, - '#size' => 60, - '#maxlength' => 128, - '#autocomplete_route_name' => FALSE, - '#process' => array( - array($class, 'processAutocomplete'), - array($class, 'processAjaxForm'), - array($class, 'processPattern'), - array($class, 'processGroup'), - ), - '#pre_render' => array( - array($class, 'preRenderTextfield'), - array($class, 'preRenderGroup'), - ), - '#theme' => 'input__textfield', - '#theme_wrappers' => array('form_element'), - ); + public static function processAjaxForm(&$element, &$form_state, &$complete_form) { + $element = ajax_pre_render_element($element); + if (!empty($element['#ajax_processed'])) { + $form_state['cache'] = TRUE; + } + return $element; + } + + /** + * Arranges elements into groups. + * + * @param array $element + * An associative array containing the properties and children of the + * element. Note that $element must be taken by reference here, so processed + * child elements are taken over into $form_state. + * @param array $form_state + * An associative array containing the current state of the form. + * @param array $complete_form + * The complete form structure. + * + * @return array + * The processed element. + */ + public static function processGroup(&$element, &$form_state, &$complete_form) { + $parents = implode('][', $element['#parents']); + + // Each details element forms a new group. The #type 'vertical_tabs' basically + // only injects a new details element. + $form_state['groups'][$parents]['#group_exists'] = TRUE; + $element['#groups'] = &$form_state['groups']; + + // Process vertical tabs group member details elements. + if (isset($element['#group'])) { + // Add this details element to the defined group (by reference). + $group = $element['#group']; + $form_state['groups'][$group][] = &$element; + } + + return $element; } /** * #process callback for #pattern form element property. * - * @param $element + * @param array $element * An associative array containing the properties and children of the * generic input element. - * @param $form_state - * The $form_state array for the form this element belongs to. + * @param array $form_state + * An associative array containing the current state of the form. + * @param array $complete_form + * The complete form structure. * - * @return + * @return array * The processed element. * * @see form_validate_pattern() */ - public static function processPattern($element, &$form_state) { + public static function processPattern(&$element, &$form_state, &$complete_form) { if (isset($element['#pattern']) && !isset($element['#attributes']['pattern'])) { $element['#attributes']['pattern'] = $element['#pattern']; $element['#element_validate'][] = 'form_validate_pattern'; @@ -87,11 +115,13 @@ public static function processPattern($element, &$form_state) { * with the route name. * @param array $form_state * An associative array containing the current state of the form. + * @param array $complete_form + * The complete form structure. * * @return array * The form element. */ - public static function processAutocomplete($element, &$form_state) { + public static function processAutocomplete(&$element, &$form_state, &$complete_form) { $access = FALSE; if (!empty($element['#autocomplete_route_name'])) { $parameters = isset($element['#autocomplete_route_parameters']) ? $element['#autocomplete_route_parameters'] : array(); @@ -105,24 +135,6 @@ public static function processAutocomplete($element, &$form_state) { // Provide a data attribute for the JavaScript behavior to bind to. $element['#attributes']['data-autocomplete-path'] = $path; } - return $element; - } - - /** - * Prepares a #type 'textfield' render element for theme_input(). - * - * @param array $element - * An associative array containing the properties of the element. - * Properties used: #title, #value, #description, #size, #maxlength, - * #placeholder, #required, #attributes. - * - * @return array - * The $element with prepared variables ready for theme_input(). - */ - public static function preRenderTextfield($element) { - $element['#attributes']['type'] = 'text'; - Element::setAttributes($element, array('id', 'name', 'value', 'size', 'maxlength', 'placeholder')); - _form_set_attributes($element, array('form-text')); return $element; } diff --git a/core/lib/Drupal/Core/Render/Element/Link.php b/core/lib/Drupal/Core/Render/Element/Link.php new file mode 100644 index 0000000..d97ad4c --- /dev/null +++ b/core/lib/Drupal/Core/Render/Element/Link.php @@ -0,0 +1,93 @@ + array( + array($class, 'preRenderLink'), + ), + ); + } + + /** + * Pre-render callback: Renders a link into #markup. + * + * Doing so during pre_render gives modules a chance to alter the link parts. + * + * @param array $element + * A structured array whose keys form the arguments to l(): + * - #title: The link text to pass as argument to l(). + * - One of the following + * - #route_name and (optionally) and a #route_parameters array; The route + * name and route parameters which will be passed into the link generator. + * - #href: The system path or URL to pass as argument to l(). + * - #options: (optional) An array of options to pass to l() or the link + * generator. + * + * @return array + * The passed-in element containing a rendered link in '#markup'. + */ + public static function preRenderLink($element) { + // By default, link options to pass to l() are normally set in #options. + $element += array('#options' => array()); + // However, within the scope of renderable elements, #attributes is a valid + // way to specify attributes, too. Take them into account, but do not override + // attributes from #options. + if (isset($element['#attributes'])) { + $element['#options'] += array('attributes' => array()); + $element['#options']['attributes'] += $element['#attributes']; + } + + // This #pre_render callback can be invoked from inside or outside of a Form + // API context, and depending on that, a HTML ID may be already set in + // different locations. #options should have precedence over Form API's #id. + // #attributes have been taken over into #options above already. + if (isset($element['#options']['attributes']['id'])) { + $element['#id'] = $element['#options']['attributes']['id']; + } + elseif (isset($element['#id'])) { + $element['#options']['attributes']['id'] = $element['#id']; + } + + // Conditionally invoke ajax_pre_render_element(), if #ajax is set. + if (isset($element['#ajax']) && !isset($element['#ajax_processed'])) { + // If no HTML ID was found above, automatically create one. + if (!isset($element['#id'])) { + $element['#id'] = $element['#options']['attributes']['id'] = drupal_html_id('ajax-link'); + } + // If #ajax['path] was not specified, use the href as Ajax request URL. + if (!isset($element['#ajax']['path'])) { + $element['#ajax']['path'] = $element['#href']; + $element['#ajax']['options'] = $element['#options']; + } + $element = ajax_pre_render_element($element); + } + + if (isset($element['#route_name'])) { + $element['#route_parameters'] = empty($element['#route_parameters']) ? array() : $element['#route_parameters']; + $element['#markup'] = \Drupal::linkGenerator()->generate($element['#title'], $element['#route_name'], $element['#route_parameters'], $element['#options']); + } + else { + $element['#markup'] = l($element['#title'], $element['#href'], $element['#options']); + } + return $element; + } + +} diff --git a/core/lib/Drupal/Core/Render/Element/MachineName.php b/core/lib/Drupal/Core/Render/Element/MachineName.php new file mode 100644 index 0000000..457bb62 --- /dev/null +++ b/core/lib/Drupal/Core/Render/Element/MachineName.php @@ -0,0 +1,216 @@ + TRUE, + '#default_value' => NULL, + '#required' => TRUE, + '#maxlength' => 64, + '#size' => 60, + '#autocomplete_route_name' => FALSE, + '#process' => array( + array($class, 'processMachineName'), + array($class, 'processAutocomplete'), + array($class, 'processAjaxForm'), + ), + '#element_validate' => array( + array($class, 'validateMachineName'), + ), + '#pre_render' => array( + array($class, 'preRenderTextfield'), + ), + '#theme' => 'input__textfield', + '#theme_wrappers' => array('form_element'), + ); + } + + /** + * Processes a machine-readable name form element. + * + * @param array $element + * The form element to process. Properties used: + * - #machine_name: An associative array containing: + * - exists: A callable to invoke for checking whether a submitted machine + * name value already exists. The submitted value is passed as an + * argument. In most cases, an existing API or menu argument loader + * function can be re-used. The callback is only invoked, if the submitted + * value differs from the element's #default_value. + * - source: (optional) The #array_parents of the form element containing + * the human-readable name (i.e., as contained in the $form structure) to + * use as source for the machine name. Defaults to array('label'). + * - label: (optional) A text to display as label for the machine name value + * after the human-readable name form element. Defaults to "Machine name". + * - replace_pattern: (optional) A regular expression (without delimiters) + * matching disallowed characters in the machine name. Defaults to + * '[^a-z0-9_]+'. + * - replace: (optional) A character to replace disallowed characters in the + * machine name via JavaScript. Defaults to '_' (underscore). When using a + * different character, 'replace_pattern' needs to be set accordingly. + * - error: (optional) A custom form error message string to show, if the + * machine name contains disallowed characters. + * - standalone: (optional) Whether the live preview should stay in its own + * form element rather than in the suffix of the source element. Defaults + * to FALSE. + * - #maxlength: (optional) Should be set to the maximum allowed length of the + * machine name. Defaults to 64. + * - #disabled: (optional) Should be set to TRUE in case an existing machine + * name must not be changed after initial creation. + * @param array $form_state + * An associative array containing the current state of the form. + * @param array $complete_form + * The complete form structure. + * + * @return array + * The processed element. + */ + public static function processMachineName(&$element, &$form_state, &$complete_form) { + // We need to pass the langcode to the client. + $language = \Drupal::languageManager()->getCurrentLanguage(); + + // Apply default form element properties. + $element += array( + '#title' => t('Machine-readable name'), + '#description' => t('A unique machine-readable name. Can only contain lowercase letters, numbers, and underscores.'), + '#machine_name' => array(), + '#field_prefix' => '', + '#field_suffix' => '', + '#suffix' => '', + ); + // A form element that only wants to set one #machine_name property (usually + // 'source' only) would leave all other properties undefined, if the defaults + // were defined in hook_element_info(). Therefore, we apply the defaults here. + $element['#machine_name'] += array( + 'source' => array('label'), + 'target' => '#' . $element['#id'], + 'label' => t('Machine name'), + 'replace_pattern' => '[^a-z0-9_]+', + 'replace' => '_', + 'standalone' => FALSE, + 'field_prefix' => $element['#field_prefix'], + 'field_suffix' => $element['#field_suffix'], + ); + + // By default, machine names are restricted to Latin alphanumeric characters. + // So, default to LTR directionality. + if (!isset($element['#attributes'])) { + $element['#attributes'] = array(); + } + $element['#attributes'] += array('dir' => 'ltr'); + + // The source element defaults to array('name'), but may have been overidden. + if (empty($element['#machine_name']['source'])) { + return $element; + } + + // Retrieve the form element containing the human-readable name from the + // complete form in $form_state. By reference, because we may need to append + // a #field_suffix that will hold the live preview. + $key_exists = NULL; + $source = NestedArray::getValue($form_state['complete_form'], $element['#machine_name']['source'], $key_exists); + if (!$key_exists) { + return $element; + } + + $suffix_id = $source['#id'] . '-machine-name-suffix'; + $element['#machine_name']['suffix'] = '#' . $suffix_id; + + if ($element['#machine_name']['standalone']) { + $element['#suffix'] = SafeMarkup::set($element['#suffix'] . '  '); + } + else { + // Append a field suffix to the source form element, which will contain + // the live preview of the machine name. + $source += array('#field_suffix' => ''); + $source['#field_suffix'] = SafeMarkup::set($source['#field_suffix'] . '  '); + + $parents = array_merge($element['#machine_name']['source'], array('#field_suffix')); + NestedArray::setValue($form_state['complete_form'], $parents, $source['#field_suffix']); + } + + $js_settings = array( + 'type' => 'setting', + 'data' => array( + 'machineName' => array( + '#' . $source['#id'] => $element['#machine_name'], + ), + 'langcode' => $language->id, + ), + ); + $element['#attached']['library'][] = 'core/drupal.machine-name'; + $element['#attached']['js'][] = $js_settings; + + return $element; + } + + /** + * Form element validation handler for machine_name elements. + * + * Note that #maxlength is validated by _form_validate() already. + * + * This checks that the submitted value + * - does not contain the replacement character only + * - does not contain disallowed characters + * - is unique; i.e., does not already exist + * - does not exceed the maximum length (via #maxlength) + * - cannot be changed after creation (via #disabled) + */ + public static function validateMachineName(&$element, &$form_state, &$complete_form) { + // Verify that the machine name not only consists of replacement tokens. + if (preg_match('@^' . $element['#machine_name']['replace'] . '+$@', $element['#value'])) { + form_error($element, $form_state, t('The machine-readable name must contain unique characters.')); + } + + // Verify that the machine name contains no disallowed characters. + if (preg_match('@' . $element['#machine_name']['replace_pattern'] . '@', $element['#value'])) { + if (!isset($element['#machine_name']['error'])) { + // Since a hyphen is the most common alternative replacement character, + // a corresponding validation error message is supported here. + if ($element['#machine_name']['replace'] == '-') { + form_error($element, $form_state, t('The machine-readable name must contain only lowercase letters, numbers, and hyphens.')); + } + // Otherwise, we assume the default (underscore). + else { + form_error($element, $form_state, t('The machine-readable name must contain only lowercase letters, numbers, and underscores.')); + } + } + else { + form_error($element, $form_state, $element['#machine_name']['error']); + } + } + + // Verify that the machine name is unique. + if ($element['#default_value'] !== $element['#value']) { + $function = $element['#machine_name']['exists']; + if (call_user_func($function, $element['#value'], $element, $form_state)) { + form_error($element, $form_state, t('The machine-readable name is already in use. It must be unique.')); + } + } + } + +} diff --git a/core/lib/Drupal/Core/Render/ElementBase.php b/core/lib/Drupal/Core/Render/Element/RenderElement.php similarity index 92% rename from core/lib/Drupal/Core/Render/ElementBase.php rename to core/lib/Drupal/Core/Render/Element/RenderElement.php index 9061aed..cf6ad20 100644 --- a/core/lib/Drupal/Core/Render/ElementBase.php +++ b/core/lib/Drupal/Core/Render/Element/RenderElement.php @@ -2,12 +2,13 @@ /** * @file - * Contains \Drupal\Core\Render\ElementBase. + * Contains \Drupal\Core\Render\Element\RenderElement. */ -namespace Drupal\Core\Render; +namespace Drupal\Core\Render\Element; use Drupal\Core\Plugin\PluginBase; +use Drupal\Core\Render\Element; /** * Provides a base class for element plugins. @@ -18,7 +19,7 @@ * * @ingroup plugin_api */ -abstract class ElementBase extends PluginBase implements ElementInterface { +abstract class RenderElement extends PluginBase implements ElementInterface { /** * Adds members of this group as actual elements for rendering. diff --git a/core/lib/Drupal/Core/Render/Element/Textfield.php b/core/lib/Drupal/Core/Render/Element/Textfield.php index be9a870..7799a03 100644 --- a/core/lib/Drupal/Core/Render/Element/Textfield.php +++ b/core/lib/Drupal/Core/Render/Element/Textfield.php @@ -8,14 +8,13 @@ namespace Drupal\Core\Render\Element; use Drupal\Core\Render\Element; -use Drupal\Core\Render\FormElementBase; /** * Formats a single-line text field. * * @FormElement("textfield") */ -class Textfield extends FormElementBase { +class Textfield extends FormElement { /** * {@inheritdoc} @@ -43,72 +42,6 @@ public function getInfo() { } /** - * #process callback for #pattern form element property. - * - * @param $element - * An associative array containing the properties and children of the - * generic input element. - * @param $form_state - * The $form_state array for the form this element belongs to. - * - * @return - * The processed element. - * - * @see form_validate_pattern() - */ - public static function processPattern($element, &$form_state) { - if (isset($element['#pattern']) && !isset($element['#attributes']['pattern'])) { - $element['#attributes']['pattern'] = $element['#pattern']; - $element['#element_validate'][] = 'form_validate_pattern'; - } - - return $element; - } - - /** - * Adds autocomplete functionality to elements with a valid - * #autocomplete_route_name. - * - * Suppose your autocomplete route name is 'mymodule.autocomplete' and its path - * is: '/mymodule/autocomplete/{a}/{b}' - * In your form you have: - * @code - * '#autocomplete_route_name' => 'mymodule.autocomplete', - * '#autocomplete_route_parameters' => array('a' => $some_key, 'b' => $some_id), - * @endcode - * The user types in "keywords" so the full path called is: - * 'mymodule_autocomplete/$some_key/$some_id?q=keywords' - * - * @param array $element - * The form element to process. Properties used: - * - #autocomplete_route_name: A route to be used as callback URL by the - * autocomplete JavaScript library. - * - #autocomplete_route_parameters: The parameters to be used in conjunction - * with the route name. - * @param array $form_state - * An associative array containing the current state of the form. - * - * @return array - * The form element. - */ - public static function processAutocomplete($element, &$form_state) { - $access = FALSE; - if (!empty($element['#autocomplete_route_name'])) { - $parameters = isset($element['#autocomplete_route_parameters']) ? $element['#autocomplete_route_parameters'] : array(); - - $path = \Drupal::urlGenerator()->generate($element['#autocomplete_route_name'], $parameters); - $access = \Drupal::service('access_manager')->checkNamedRoute($element['#autocomplete_route_name'], $parameters, \Drupal::currentUser()); - } - if ($access) { - $element['#attributes']['class'][] = 'form-autocomplete'; - $element['#attached']['library'][] = 'core/drupal.autocomplete'; - // Provide a data attribute for the JavaScript behavior to bind to. - $element['#attributes']['data-autocomplete-path'] = $path; - } - return $element; - } - - /** * Prepares a #type 'textfield' render element for theme_input(). * * @param array $element diff --git a/core/lib/Drupal/Core/Render/ElementInfoManager.php b/core/lib/Drupal/Core/Render/ElementInfoManager.php index 418436a..43d0b86 100644 --- a/core/lib/Drupal/Core/Render/ElementInfoManager.php +++ b/core/lib/Drupal/Core/Render/ElementInfoManager.php @@ -75,7 +75,7 @@ protected function buildInfo() { /** * {@inheritdoc} * - * @return \Drupal\Core\Render\ElementInterface + * @return \Drupal\Core\Render\Element\ElementInterface */ public function createInstance($plugin_id, array $configuration = array()) { return parent::createInstance($plugin_id, $configuration); diff --git a/core/lib/Drupal/Core/Render/FormElementBase.php b/core/lib/Drupal/Core/Render/FormElementBase.php deleted file mode 100644 index 28ff981..0000000 --- a/core/lib/Drupal/Core/Render/FormElementBase.php +++ /dev/null @@ -1,65 +0,0 @@ - 'input__color', '#theme_wrappers' => array('form_element'), ); - $types['machine_name'] = array( - '#input' => TRUE, - '#default_value' => NULL, - '#required' => TRUE, - '#maxlength' => 64, - '#size' => 60, - '#autocomplete_route_name' => FALSE, - '#process' => array('form_process_machine_name', 'form_process_autocomplete', 'ajax_process_form'), - '#element_validate' => array('form_validate_machine_name'), - '#pre_render' => array('form_pre_render_textfield'), - '#theme' => 'input__textfield', - '#theme_wrappers' => array('form_element'), - ); $types['password'] = array( '#input' => TRUE, '#size' => 60, @@ -568,9 +555,6 @@ function system_element_info() { $types['value'] = array( '#input' => TRUE, ); - $types['link'] = array( - '#pre_render' => array('drupal_pre_render_link'), - ); $types['fieldset'] = array( '#value' => NULL, '#process' => array('form_process_group', 'ajax_process_form'),