Issue #2305839 by jhodgdon, tim.plunkett: Convert hook_element_info() to annotated classes.

8.0.x
webchick 2014-08-12 12:19:20 -07:00
parent fef9fd278d
commit 9c87059f0f
25 changed files with 1244 additions and 498 deletions

View File

@ -749,6 +749,9 @@ services:
plugin.manager.condition:
class: Drupal\Core\Condition\ConditionManager
parent: default_plugin_manager
plugin.manager.element_info:
class: Drupal\Core\Render\ElementInfoManager
parent: default_plugin_manager
kernel_destruct_subscriber:
class: Drupal\Core\EventSubscriber\KernelDestructionSubscriber
tags:
@ -907,8 +910,7 @@ services:
info_parser:
class: Drupal\Core\Extension\InfoParser
element_info:
class: Drupal\Core\Render\ElementInfo
arguments: ['@module_handler']
alias: plugin.manager.element_info
file.mime_type.guesser:
class: Drupal\Core\File\MimeType\MimeTypeGuesser
tags:

View File

@ -6,6 +6,7 @@
*/
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
/**
* @defgroup ajax Ajax API
@ -162,20 +163,10 @@ use Drupal\Core\Form\FormStateInterface;
/**
* 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, FormStateInterface $form_state) {
$element = ajax_pre_render_element($element);
if (!empty($element['#ajax_processed'])) {
$form_state['cache'] = TRUE;
}
return $element;
function ajax_process_form($element, FormStateInterface $form_state, &$complete_form) {
return Element\FormElement::processAjaxForm($element, $form_state, $complete_form);
}
/**

View File

@ -2670,65 +2670,10 @@ 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'.
* @deprecated Use \Drupal\Core\Render\Element\Link::preRenderLink().
*/
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;
return Element\Link::preRenderLink($element);
}
/**

View File

@ -566,22 +566,10 @@ function form_type_select_value($element, $input = FALSE) {
/**
* Determines the value for a textfield form element.
*
* @param $element
* The form element whose value is being populated.
* @param $input
* The incoming input to populate the form element. If this is FALSE,
* the element's default value should be returned.
*
* @return
* The data that will appear in the $element_state['values'] collection
* for this element. Return nothing to use the default.
* @deprecated Use \Drupal\Core\Render\Element\Textfield::valueCallback().
*/
function form_type_textfield_value($element, $input = FALSE) {
if ($input !== FALSE && $input !== NULL) {
// Equate $input to the form value to ensure it's marked for
// validation.
return str_replace(array("\r", "\n"), '', $input);
}
function form_type_textfield_value(&$element, $input, &$form_state) {
return Element\Textfield::valueCallback($element, $input, $form_state);
}
/**
@ -1306,24 +1294,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 current state of the form 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, FormStateInterface $form_state) {
if (isset($element['#pattern']) && !isset($element['#attributes']['pattern'])) {
$element['#attributes']['pattern'] = $element['#pattern'];
$element['#element_validate'][] = 'form_validate_pattern';
}
return $element;
function form_process_pattern($element, FormStateInterface $form_state, &$complete_form) {
return Element\FormElement::processPattern($element, $form_state, $complete_form);
}
/**
@ -1922,61 +1896,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) {
// The element may be rendered outside of a Form API context.
if (!isset($element['#parents']) || !isset($element['#groups'])) {
return $element;
}
// Inject group member elements belonging to this group.
$parents = implode('][', $element['#parents']);
$children = Element::children($element['#groups'][$parents]);
if (!empty($children)) {
foreach ($children as $key) {
// Break references and indicate that the element should be rendered as
// group member.
$child = (array) $element['#groups'][$parents][$key];
$child['#group_details'] = TRUE;
// Inject the element as new child element.
$element[] = $child;
$sort = TRUE;
}
// Re-sort the element's children if we injected group member elements.
if (isset($sort)) {
$element['#sorted'] = FALSE;
}
}
if (isset($element['#group'])) {
// Contains form element summary functionalities.
$element['#attached']['library'][] = 'core/drupal.form';
$group = $element['#group'];
// If this element belongs to a group, but the group-holding element does
// not exist, we need to render it (at its original location).
if (!isset($element['#groups'][$group]['#group_exists'])) {
// Intentionally empty to clarify the flow; we simply return $element.
}
// If we injected this element into the group, then we want to render it.
elseif (!empty($element['#group_details'])) {
// Intentionally empty to clarify the flow; we simply return $element.
}
// Otherwise, this element belongs to a group and the group exists, so we do
// not render it.
elseif (Element::children($element['#groups'][$group])) {
$element['#printed'] = TRUE;
}
}
return $element;
return Element\RenderElement::preRenderGroup($element);
}
/**
@ -2064,43 +1987,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 \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return array
* The form element.
* @deprecated Use \Drupal\Core\Render\Element\FormElement::processAutocomplete().
*/
function form_process_autocomplete($element, FormStateInterface $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;
function form_process_autocomplete($element, FormStateInterface $form_state, &$complete_form) {
return Element\FormElement::processAutocomplete($element, $form_state, $complete_form);
}
/**
@ -2206,20 +2096,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) {
$element['#attributes']['type'] = 'text';
Element::setAttributes($element, array('id', 'name', 'value', 'size', 'maxlength', 'placeholder'));
_form_set_attributes($element, array('form-text'));
return $element;
return Element\Textfield::preRenderTextfield($element);
}
/**

View File

@ -938,7 +938,14 @@ class FormBuilder implements FormBuilderInterface, FormValidatorInterface, FormS
// Set the element's #value property.
if (!isset($element['#value']) && !array_key_exists('#value', $element)) {
// @todo Once all elements are converted to plugins in
// https://www.drupal.org/node/2311393, rely on
// $element['#value_callback'] directly.
$value_callable = !empty($element['#value_callback']) ? $element['#value_callback'] : 'form_type_' . $element['#type'] . '_value';
if (!is_callable($value_callable)) {
$value_callable = '\Drupal\Core\Render\Element\FormElement::valueCallback';
}
if ($process_input) {
// Get the input for the current element. NULL values in the input need
// to be explicitly distinguished from missing input. (see below)
@ -962,9 +969,8 @@ class FormBuilder implements FormBuilderInterface, FormValidatorInterface, FormS
// If we have input for the current element, assign it to the #value
// property, optionally filtered through $value_callback.
if ($input_exists) {
if (is_callable($value_callable)) {
$element['#value'] = call_user_func_array($value_callable, array(&$element, $input, &$form_state));
}
$element['#value'] = call_user_func_array($value_callable, array(&$element, $input, &$form_state));
if (!isset($element['#value']) && isset($input)) {
$element['#value'] = $input;
}
@ -978,9 +984,8 @@ class FormBuilder implements FormBuilderInterface, FormValidatorInterface, FormS
if (!isset($element['#value'])) {
// Call #type_value without a second argument to request default_value
// handling.
if (is_callable($value_callable)) {
$element['#value'] = call_user_func_array($value_callable, array(&$element, FALSE, &$form_state));
}
$element['#value'] = call_user_func_array($value_callable, array(&$element, FALSE, &$form_state));
// Final catch. If we haven't set a value yet, use the explicit default
// value. Avoid image buttons (which come with garbage value), so we
// only get value for the button actually clicked.

View File

@ -0,0 +1,32 @@
<?php
/**
* @file
* Contains \Drupal\Core\Render\Annotation\FormElement.
*/
namespace Drupal\Core\Render\Annotation;
/**
* Defines a form element plugin annotation object.
*
* See \Drupal\Core\Render\Element\FormElementInterface for more information
* about form element plugins.
*
* Plugin Namespace: Element
*
* For a working example, see \Drupal\Core\Render\Element\Textfield.
*
* @see \Drupal\Core\Render\ElementInfoManager
* @see \Drupal\Core\Render\Element\FormElementInterface
* @see \Drupal\Core\Render\Element\FormElement
* @see \Drupal\Core\Render\Annotation\RenderElement
* @see plugin_api
*
* @ingroup theme_render
*
* @Annotation
*/
class FormElement extends RenderElement {
}

View File

@ -0,0 +1,34 @@
<?php
/**
* @file
* Contains \Drupal\Core\Render\Annotation\RenderElement.
*/
namespace Drupal\Core\Render\Annotation;
use Drupal\Component\Annotation\PluginID;
/**
* Defines a render element plugin annotation object.
*
* See \Drupal\Core\Render\Element\ElementInterface for more information
* about render element plugins.
*
* Plugin Namespace: Element
*
* For a working example, see \Drupal\Core\Render\Element\Link.
*
* @see \Drupal\Core\Render\ElementInfoManager
* @see \Drupal\Core\Render\Element\ElementInterface
* @see \Drupal\Core\Render\Element\RenderElement
* @see \Drupal\Core\Render\Annotation\FormElement
* @see plugin_api
*
* @ingroup theme_render
*
* @Annotation
*/
class RenderElement extends PluginID {
}

View File

@ -10,7 +10,11 @@ namespace Drupal\Core\Render;
use Drupal\Component\Utility\String;
/**
* Deals with drupal render elements.
* Provides helper methods for Drupal render elements.
*
* @see \Drupal\Core\Render\Element\ElementInterface
*
* @ingroup theme_render
*/
class Element {
@ -47,7 +51,7 @@ class Element {
* The key to check.
*
* @return bool
* TRUE if the element is a child, FALSE otherwise.
* TRUE if the element is a child, FALSE otherwise.
*/
public static function child($key) {
return !isset($key[0]) || $key[0] != '#';
@ -143,11 +147,11 @@ class Element {
* @param array $element
* The renderable element to process. Passed by reference.
* @param array $map
* An associative array whose keys are element property names and whose values
* are the HTML attribute names to set for corresponding the property; e.g.,
* array('#propertyname' => 'attributename'). If both names are identical
* except for the leading '#', then an attribute name value is sufficient and
* no property name needs to be specified.
* An associative array whose keys are element property names and whose
* values are the HTML attribute names to set on the corresponding
* property; e.g., array('#propertyname' => 'attributename'). If both names
* are identical except for the leading '#', then an attribute name value is
* sufficient and no property name needs to be specified.
*/
public static function setAttributes(array &$element, array $map) {
foreach ($map as $property => $attribute) {

View File

@ -0,0 +1,45 @@
<?php
/**
* @file
* Contains \Drupal\Core\Render\Element\ElementInterface.
*/
namespace Drupal\Core\Render\Element;
use Drupal\Component\Plugin\PluginInspectionInterface;
/**
* Provides an interface for element plugins.
*
* Render element plugins allow modules to declare their own Render API element
* types and specify the default values for the properties. The values returned
* by the getInfo() method of the element plugin will be merged with the
* properties specified in render arrays. Thus, you can specify defaults for any
* Render API keys, in addition to those explicitly documented by
* \Drupal\Core\Render\ElementInfoManagerInterface::getInfo().
*
* Some render elements are specifically form input elements; see
* \Drupal\Core\Render\Element\FormElementInterface for more information.
*
* @see \Drupal\Core\Render\ElementInfoManager
* @see \Drupal\Core\Render\Annotation\RenderElement
* @see \Drupal\Core\Render\Element\RenderElement
* @see plugin_api
*
* @ingroup theme_render
*/
interface ElementInterface extends PluginInspectionInterface {
/**
* Returns the element properties for this element.
*
* @return array
* An array of element properties. See
* \Drupal\Core\Render\ElementInfoManagerInterface::getInfo() for
* documentation of the standard properties of all elements, and the
* return value format.
*/
public function getInfo();
}

View File

@ -0,0 +1,162 @@
<?php
/**
* @file
* Contains \Drupal\Core\Render\Element\FormElement.
*/
namespace Drupal\Core\Render\Element;
use Drupal\Core\Form\FormStateInterface;
/**
* Provides a base class for form render plugins.
*
* @see \Drupal\Core\Render\Annotation\FormElement
* @see \Drupal\Core\Render\Element\FormElementInterface
* @see \Drupal\Core\Render\ElementInfoManager
* @see plugin_api
*
* @ingroup theme_render
*/
abstract class FormElement extends RenderElement implements FormElementInterface {
/**
* {@inheritdoc}
*/
public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
return NULL;
}
/**
* Form element processing handler for the #ajax form property.
*
* @param array $element
* An associative array containing the properties of the element.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param array $complete_form
* The complete form structure.
*
* @return array
* The processed element.
*
* @see ajax_pre_render_element()
*/
public static function processAjaxForm(&$element, FormStateInterface $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 \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param array $complete_form
* The complete form structure.
*
* @return array
* The processed element.
*/
public static function processGroup(&$element, FormStateInterface $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 array $element
* An associative array containing the properties and children of the
* generic input element.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param array $complete_form
* The complete form structure.
*
* @return array
* The processed element.
*
* @see form_validate_pattern()
*/
public static function processPattern(&$element, FormStateInterface $form_state, &$complete_form) {
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.
*
* This sets up autocomplete functionality for elements with an
* #autocomplete_route_name property, using the #autocomplete_route_parameters
* property if present.
*
* For example, suppose your autocomplete route name is
* 'mymodule.autocomplete' and its path is
* '/mymodule/autocomplete/{a}/{b}'. In a form array, you would create a text
* field with properties:
* @code
* '#autocomplete_route_name' => 'mymodule.autocomplete',
* '#autocomplete_route_parameters' => array('a' => $some_key, 'b' => $some_id),
* @endcode
* If the user types "keywords" in that field, the full path called would be:
* '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 \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param array $complete_form
* The complete form structure.
*
* @return array
* The form element.
*/
public static function processAutocomplete(&$element, FormStateInterface $form_state, &$complete_form) {
$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;
}
}

View File

@ -0,0 +1,46 @@
<?php
/**
* @file
* Contains \Drupal\Core\Render\Element\FormElementInterface.
*/
namespace Drupal\Core\Render\Element;
use Drupal\Core\Form\FormStateInterface;
/**
* Provides an interface for form element plugins.
*
* Form element plugins are a subset of render elements, specifically
* representing HTML elements that take input as part of a form. Form element
* plugins are discovered via the same mechanism as regular render element
* plugins. See \Drupal\Core\Render\Element\ElementInterface for general
* information about render element plugins.
*
* @see \Drupal\Core\Render\ElementInfoManager
* @see \Drupal\Core\Render\Element\FormElement
* @see \Drupal\Core\Render\Annotation\FormElement
* @see plugin_api
*
* @ingroup theme_render
*/
interface FormElementInterface extends ElementInterface {
/**
* Determines how user input is mapped to an element's #value property.
*
* @param array $element
* An associative array containing the properties of the element.
* @param mixed $input
* The incoming input to populate the form element. If this is FALSE,
* the element's default value should be returned.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return mixed
* The value to assign to the element.
*/
public static function valueCallback(&$element, $input, FormStateInterface $form_state);
}

View File

@ -0,0 +1,94 @@
<?php
/**
* @file
* Contains \Drupal\Core\Render\Element\Link.
*/
namespace Drupal\Core\Render\Element;
/**
* Provides a link render element.
*
* @RenderElement("link")
*/
class Link extends RenderElement {
/**
* {@inheritdoc}
*/
public function getInfo() {
$class = get_class($this);
return array(
'#pre_render' => 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) 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;
}
}

View File

@ -0,0 +1,226 @@
<?php
/**
* @file
* Contains \Drupal\Core\Render\Element\MachineName.
*/
namespace Drupal\Core\Render\Element;
use Drupal\Component\Utility\NestedArray;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Form\FormStateInterface;
/**
* Provides a machine name render element.
*
* Provides a form element to enter a machine name, which is validated to ensure
* that the name is unique and does not contain disallowed characters. All
* disallowed characters are replaced with a replacement character via
* JavaScript.
*
* @FormElement("machine_name")
*/
class MachineName extends Textfield {
/**
* {@inheritdoc}
*/
public function getInfo() {
$class = get_class($this);
return array(
'#input' => TRUE,
'#default_value' => NULL,
'#required' => TRUE,
'#maxlength' => 64,
'#size' => 60,
'#autocomplete_route_name' => FALSE,
'#process' => array(
array($class, 'processMachineName'),
array($class, 'processAutocomplete'),
array($class, 'processAjaxForm'),
),
'#element_validate' => array(
array($class, 'validateMachineName'),
),
'#pre_render' => array(
array($class, 'preRenderTextfield'),
),
'#theme' => 'input__textfield',
'#theme_wrappers' => array('form_element'),
);
}
/**
* {@inheritdoc}
*/
public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
return NULL;
}
/**
* Processes a machine-readable name form element.
*
* @param array $element
* The form element to process. Properties used:
* - #machine_name: An associative array containing:
* - exists: A callable to invoke for checking whether a submitted machine
* name value already exists. The submitted value is passed as an
* argument. In most cases, an existing API or menu argument loader
* function can be re-used. The callback is only invoked if the
* submitted value differs from the element's #default_value.
* - source: (optional) The #array_parents of the form element containing
* the human-readable name (i.e., as contained in the $form structure)
* to use as source for the machine name. Defaults to array('label').
* - label: (optional) Text to display as label for the machine name value
* after the human-readable name form element. Defaults to "Machine
* name".
* - replace_pattern: (optional) A regular expression (without delimiters)
* matching disallowed characters in the machine name. Defaults to
* '[^a-z0-9_]+'.
* - replace: (optional) A character to replace disallowed characters in
* the machine name via JavaScript. Defaults to '_' (underscore). When
* using a different character, 'replace_pattern' needs to be set
* accordingly.
* - error: (optional) A custom form error message string to show, if the
* machine name contains disallowed characters.
* - standalone: (optional) Whether the live preview should stay in its
* own form element rather than in the suffix of the source
* element. Defaults to FALSE.
* - #maxlength: (optional) Maximum allowed length of the machine name.
* Defaults to 64.
* - #disabled: (optional) Should be set to TRUE if an existing machine
* name must not be changed after initial creation.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param array $complete_form
* The complete form structure.
*
* @return array
* The processed element.
*/
public static function processMachineName(&$element, FormStateInterface $form_state, &$complete_form) {
// We need to pass the langcode to the client.
$language = \Drupal::languageManager()->getCurrentLanguage();
// Apply default form element properties.
$element += array(
'#title' => t('Machine-readable name'),
'#description' => t('A unique machine-readable name. Can only contain lowercase letters, numbers, and underscores.'),
'#machine_name' => array(),
'#field_prefix' => '',
'#field_suffix' => '',
'#suffix' => '',
);
// A form element that only wants to set one #machine_name property (usually
// 'source' only) would leave all other properties undefined, if the defaults
// were defined in hook_element_info(). Therefore, we apply the defaults here.
$element['#machine_name'] += array(
'source' => array('label'),
'target' => '#' . $element['#id'],
'label' => t('Machine name'),
'replace_pattern' => '[^a-z0-9_]+',
'replace' => '_',
'standalone' => FALSE,
'field_prefix' => $element['#field_prefix'],
'field_suffix' => $element['#field_suffix'],
);
// By default, machine names are restricted to Latin alphanumeric characters.
// So, default to LTR directionality.
if (!isset($element['#attributes'])) {
$element['#attributes'] = array();
}
$element['#attributes'] += array('dir' => 'ltr');
// 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'] . ' <small id="' . $suffix_id . '">&nbsp;</small>');
}
else {
// Append a field suffix to the source form element, which will contain
// the live preview of the machine name.
$source += array('#field_suffix' => '');
$source['#field_suffix'] = SafeMarkup::set($source['#field_suffix'] . ' <small id="' . $suffix_id . '">&nbsp;</small>');
$parents = array_merge($element['#machine_name']['source'], array('#field_suffix'));
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, FormStateInterface $form_state, &$complete_form) {
// Verify that the machine name not only consists of replacement tokens.
if (preg_match('@^' . $element['#machine_name']['replace'] . '+$@', $element['#value'])) {
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.'));
}
}
}
}

View File

@ -0,0 +1,85 @@
<?php
/**
* @file
* Contains \Drupal\Core\Render\Element\RenderElement.
*/
namespace Drupal\Core\Render\Element;
use Drupal\Core\Plugin\PluginBase;
use Drupal\Core\Render\Element;
/**
* Provides a base class for element render plugins.
*
* @see \Drupal\Core\Render\Annotation\RenderElement
* @see \Drupal\Core\Render\ElementInterface
* @see \Drupal\Core\Render\ElementInfoManager
* @see plugin_api
*
* @ingroup theme_render
*/
abstract class RenderElement extends PluginBase implements ElementInterface {
/**
* Adds members of this group as actual elements for rendering.
*
* @param array $element
* An associative array containing the properties and children of the
* element.
*
* @return array
* The modified element with all group members.
*/
public static function preRenderGroup($element) {
// The element may be rendered outside of a Form API context.
if (!isset($element['#parents']) || !isset($element['#groups'])) {
return $element;
}
// Inject group member elements belonging to this group.
$parents = implode('][', $element['#parents']);
$children = Element::children($element['#groups'][$parents]);
if (!empty($children)) {
foreach ($children as $key) {
// Break references and indicate that the element should be rendered as
// group member.
$child = (array) $element['#groups'][$parents][$key];
$child['#group_details'] = TRUE;
// Inject the element as new child element.
$element[] = $child;
$sort = TRUE;
}
// Re-sort the element's children if we injected group member elements.
if (isset($sort)) {
$element['#sorted'] = FALSE;
}
}
if (isset($element['#group'])) {
// Contains form element summary functionalities.
$element['#attached']['library'][] = 'core/drupal.form';
$group = $element['#group'];
// If this element belongs to a group, but the group-holding element does
// not exist, we need to render it (at its original location).
if (!isset($element['#groups'][$group]['#group_exists'])) {
// Intentionally empty to clarify the flow; we simply return $element.
}
// If we injected this element into the group, then we want to render it.
elseif (!empty($element['#group_details'])) {
// Intentionally empty to clarify the flow; we simply return $element.
}
// Otherwise, this element belongs to a group and the group exists, so we do
// not render it.
elseif (Element::children($element['#groups'][$group])) {
$element['#printed'] = TRUE;
}
}
return $element;
}
}

View File

@ -0,0 +1,75 @@
<?php
/**
* @file
* Contains \Drupal\Core\Render\Element\Textfield.
*/
namespace Drupal\Core\Render\Element;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
/**
* Provides a one-line text field form element.
*
* @FormElement("textfield")
*/
class Textfield extends FormElement {
/**
* {@inheritdoc}
*/
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'),
);
}
/**
* {@inheritdoc}
*/
public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
if ($input !== FALSE && $input !== NULL) {
// Equate $input to the form value to ensure it's marked for
// validation.
return str_replace(array("\r", "\n"), '', $input);
}
}
/**
* 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;
}
}

View File

@ -1,65 +0,0 @@
<?php
/**
* @file
* Contains \Drupal\Core\Render\ElementInfo.
*/
namespace Drupal\Core\Render;
use Drupal\Core\Extension\ModuleHandlerInterface;
/**
* Provides the default element info implementation.
*/
class ElementInfo implements ElementInfoInterface {
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
*/
protected $moduleHandler;
/**
* Stores the available element information
*
* @var array
*/
protected $elementInfo;
/**
* Constructs a new ElementInfo instance.
*
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*/
public function __construct(ModuleHandlerInterface $module_handler) {
$this->moduleHandler = $module_handler;
}
/**
* {@inheritdoc}
*/
public function getInfo($type) {
if (!isset($this->elementInfo)) {
$this->elementInfo = $this->buildInfo();
}
return isset($this->elementInfo[$type]) ? $this->elementInfo[$type] : array();
}
/**
* Builds up all element information.
*/
protected function buildInfo() {
$info = $this->moduleHandler->invokeAll('element_info');
foreach ($info as $element_type => $element) {
$info[$element_type]['#type'] = $element_type;
}
// Allow modules to alter the element type defaults.
$this->moduleHandler->alter('element_info', $info);
return $info;
}
}

View File

@ -1,51 +0,0 @@
<?php
/**
* @file
* Contains \Drupal\Core\Render\ElementInfoInterface.
*/
namespace Drupal\Core\Render;
/**
* Defines available render array element types.
*/
interface ElementInfoInterface {
/**
* Retrieves the default properties for the defined element type.
*
* Each of the form element types defined by this hook is assumed to have
* a matching theme function, e.g. theme_elementtype(), which should be
* registered with hook_theme() as normal.
*
* For more information about custom element types see the explanation at
* http://drupal.org/node/169815.
*
* @param string $type
* An element type as defined by hook_element_info().
*
* @return array
* An associative array describing the element types being defined. The array
* contains a sub-array for each element type, with the machine-readable type
* name as the key. Each sub-array has a number of possible attributes:
* - "#input": boolean indicating whether or not this element carries a value
* (even if it's hidden).
* - "#process": array of callback functions taking $element, $form_state,
* and $complete_form.
* - "#after_build": array of callables taking $element and $form_state.
* - "#validate": array of callback functions taking $form and $form_state.
* - "#element_validate": array of callback functions taking $element and
* $form_state.
* - "#pre_render": array of callables taking $element.
* - "#post_render": array of callables taking $children and $element.
* - "#submit": array of callback functions taking $form and $form_state.
* - "#title_display": optional string indicating if and how #title should be
* displayed, see the form-element template and theme_form_element_label().
*
* @see hook_element_info()
* @see hook_element_info_alter()
*/
public function getInfo($type);
}

View File

@ -0,0 +1,100 @@
<?php
/**
* @file
* Contains \Drupal\Core\Render\ElementInfoManager.
*/
namespace Drupal\Core\Render;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\Core\Render\Element\FormElementInterface;
/**
* Provides a plugin manager for element plugins.
*
* @see \Drupal\Core\Render\Annotation\RenderElement
* @see \Drupal\Core\Render\Annotation\FormElement
* @see \Drupal\Core\Render\Element\RenderElement
* @see \Drupal\Core\Render\Element\FormElement
* @see \Drupal\Core\Render\Element\ElementInterface
* @see \Drupal\Core\Render\Element\FormElementInterface
* @see plugin_api
*/
class ElementInfoManager extends DefaultPluginManager implements ElementInfoManagerInterface {
/**
* Stores the available element information.
*
* @var array
*/
protected $elementInfo;
/**
* Constructs a ElementInfoManager object.
*
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
* Cache backend instance to use.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler to invoke the alter hook with.
*/
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
$this->setCacheBackend($cache_backend, 'element_info');
parent::__construct('Element', $namespaces, $module_handler, 'Drupal\Core\Render\Annotation\RenderElement');
}
/**
* {@inheritdoc}
*/
public function getInfo($type) {
if (!isset($this->elementInfo)) {
$this->elementInfo = $this->buildInfo();
}
return isset($this->elementInfo[$type]) ? $this->elementInfo[$type] : array();
}
/**
* Builds up all element information.
*/
protected function buildInfo() {
// @todo Remove this hook once all elements are converted to plugins in
// https://www.drupal.org/node/2311393.
$info = $this->moduleHandler->invokeAll('element_info');
foreach ($this->getDefinitions() as $element_type => $definition) {
$element = $this->createInstance($element_type);
$element_info = $element->getInfo();
// If this is element is to be used exclusively in a form, denote that it
// will receive input, and assign the value callback.
if ($element instanceof FormElementInterface) {
$element_info['#input'] = TRUE;
$element_info['#value_callback'] = array($definition['class'], 'valueCallback');
}
$info[$element_type] = $element_info;
}
foreach ($info as $element_type => $element) {
$info[$element_type]['#type'] = $element_type;
}
// Allow modules to alter the element type defaults.
$this->moduleHandler->alter('element_info', $info);
return $info;
}
/**
* {@inheritdoc}
*
* @return \Drupal\Core\Render\Element\ElementInterface
*/
public function createInstance($plugin_id, array $configuration = array()) {
return parent::createInstance($plugin_id, $configuration);
}
}

View File

@ -0,0 +1,55 @@
<?php
/**
* @file
* Contains \Drupal\Core\Render\ElementInfoManagerInterface.
*/
namespace Drupal\Core\Render;
/**
* Collects available render array element types.
*/
interface ElementInfoManagerInterface {
/**
* Retrieves the default properties for the defined element type.
*
* Each of the form element types defined by this hook is assumed to have
* a matching theme hook, which should be registered with hook_theme() as
* normal.
*
* For more information about custom element types see the explanation at
* http://drupal.org/node/169815.
*
* @param string $type
* An element type as defined by hook_element_info() or the machine name
* of an element type plugin.
*
* @return array
* An associative array describing the element types being defined. The
* array contains a sub-array for each element type, with the
* machine-readable type name as the key. Each sub-array has a number of
* possible attributes:
* - #input: boolean indicating whether or not this element carries a value
* (even if it's hidden).
* - #process: array of callback functions taking $element, $form_state,
* and $complete_form.
* - #after_build: array of callables taking $element and $form_state.
* - #validate: array of callback functions taking $form and $form_state.
* - #element_validate: array of callback functions taking $element and
* $form_state.
* - #pre_render: array of callables taking $element.
* - #post_render: array of callables taking $children and $element.
* - #submit: array of callback functions taking $form and $form_state.
* - #title_display: optional string indicating if and how #title should be
* displayed (see form-element.html.twig).
*
* @see hook_element_info()
* @see hook_element_info_alter()
* @see \Drupal\Core\Render\Element\ElementInterface
* @see \Drupal\Core\Render\Element\ElementInterface::getInfo()
*/
public function getInfo($type);
}

View File

@ -0,0 +1,4 @@
@todo This must be here because DrupalKernel will only allow namespaces to
provide plugins if there is a Plugin subdirectory, and git does not allow
empty subdirectories. This file should be removed once
https://www.drupal.org/node/2309889 is fixed.

View File

@ -193,11 +193,14 @@ function callback_queue_worker($queue_item_data) {
* specify their default values. The values returned by this hook will be
* merged with the elements returned by form constructor implementations and so
* can return defaults for any Form APIs keys in addition to those explicitly
* documented by \Drupal\Core\Render\ElementInfoInterface::getInfo().
* documented by \Drupal\Core\Render\ElementInfoManagerInterface::getInfo().
*
* @return array
* An associative array with structure identical to that of the return value
* of \Drupal\Core\Render\ElementInfoInterface::getInfo().
* of \Drupal\Core\Render\ElementInfoManagerInterface::getInfo().
*
* @deprecated Use an annotated class instead, see
* \Drupal\Core\Render\Element\ElementInterface.
*
* @see hook_element_info_alter()
* @see system_element_info()
@ -217,7 +220,7 @@ function hook_element_info() {
*
* @param array $types
* An associative array with structure identical to that of the return value
* of \Drupal\Core\Render\ElementInfoInterface::getInfo().
* of \Drupal\Core\Render\ElementInfoManagerInterface::getInfo().
*
* @see hook_element_info()
*/

View File

@ -368,16 +368,6 @@ function system_element_info() {
'#pre_render' => array('form_pre_render_image_button'),
'#theme_wrappers' => array('input__image_button'),
);
$types['textfield'] = array(
'#input' => TRUE,
'#size' => 60,
'#maxlength' => 128,
'#autocomplete_route_name' => FALSE,
'#process' => array('form_process_autocomplete', 'ajax_process_form', 'form_process_pattern', 'form_process_group'),
'#pre_render' => array('form_pre_render_textfield', 'form_pre_render_group'),
'#theme' => 'input__textfield',
'#theme_wrappers' => array('form_element'),
);
$types['tel'] = array(
'#input' => TRUE,
'#size' => 30,
@ -448,19 +438,6 @@ function system_element_info() {
'#theme' => '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,
@ -586,9 +563,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'),

View File

@ -163,11 +163,21 @@
* requests and the CSS files used to style that markup. In order to ensure that
* a theme can completely customize the markup, module developers should avoid
* directly writing HTML markup for pages, blocks, and other user-visible output
* in their modules, and instead return structured "render arrays" (described
* below). Doing this also increases usability, by ensuring that the markup used
* for similar functionality on different areas of the site is the same, which
* gives users fewer user interface patterns to learn.
* in their modules, and instead return structured "render arrays" (see @ref
* arrays below). Doing this also increases usability, by ensuring that the
* markup used for similar functionality on different areas of the site is the
* same, which gives users fewer user interface patterns to learn.
*
* For further information on the Theme and Render APIs, see:
* - https://drupal.org/documentation/theme
* - https://drupal.org/node/722174
* - https://drupal.org/node/933976
* - https://drupal.org/node/930760
*
* @todo Check these links. Some are for Drupal 7, and might need updates for
* Drupal 8.
*
* @section arrays Render arrays
* The core structure of the Render API is the render array, which is a
* hierarchical associative array containing data to be rendered and properties
* describing how the data should be rendered. A render array that is returned
@ -194,11 +204,8 @@
* - #type: Specifies that the array contains data and options for a particular
* type of "render element" (examples: 'form', for an HTML form; 'textfield',
* 'submit', and other HTML form element types; 'table', for a table with
* rows, columns, and headers). Modules define render elements by implementing
* hook_element_info(), which specifies the properties that are used in render
* arrays to provide the data and options, and default values for these
* properties. Look through implementations of hook_element_info() to discover
* what render elements are available.
* rows, columns, and headers). See @ref elements below for more on render
* element types.
* - #theme: Specifies that the array contains data to be themed by a particular
* theme hook. Modules define theme hooks by implementing hook_theme(), which
* specifies the input "variables" used to provide data and options; if a
@ -214,15 +221,29 @@
* normally preferable to use #theme or #type instead, so that the theme can
* customize the markup.
*
* For further information on the Theme and Render APIs, see:
* - https://drupal.org/documentation/theme
* - https://drupal.org/developing/modules/8
* - https://drupal.org/node/722174
* - https://drupal.org/node/933976
* - https://drupal.org/node/930760
* @section elements Render elements
* Render elements are defined by Drupal core and modules. The primary way to
* define a render element is to create a render element plugin. There are
* two types of render element plugins:
* - Generic elements: Generic render element plugins implement
* \Drupal\Core\Render\Element\ElementInterface, are annotated with
* \Drupal\Core\Render\Annotation\RenderElement annotation, go in plugin
* namespace Element, and generally extend the
* \Drupal\Core\Render\Element\RenderElement base class.
* - Form input elements: Render elements representing form input elements
* implement \Drupal\Core\Render\Element\FormElementInterface, are annotated
* with \Drupal\Core\Render\Annotation\FormElement annotation, go in plugin
* namespace Element, and generally extend the
* \Drupal\Core\Render\Element\FormElement base class.
* See the @link plugin_api Plugin API topic @endlink for general information
* on plugins, and look for classes with the RenderElement or FormElement
* annotation to discover what render elements are available.
*
* Modules can also currently define render elements by implementing
* hook_element_info(), although defining a plugin is preferred.
* properties. Look through implementations of hook_element_info() to discover
* elements defined this way.
*
* @todo Check these links. Some are for Drupal 7, and might need updates for
* Drupal 8.
* @see themeable
*
* @}

View File

@ -0,0 +1,197 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Render\ElementInfoManagerTest.
*/
namespace Drupal\Tests\Core\Render;
use Drupal\Core\Render\ElementInfoManager;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\Core\Render\ElementInfoManager
* @group Render
*/
class ElementInfoManagerTest extends UnitTestCase {
/**
* The class under test.
*
* @var \Drupal\Core\Render\ElementInfoManagerInterface
*/
protected $elementInfo;
/**
* The cache backend to use.
*
* @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $cache;
/**
* The mocked module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $moduleHandler;
/**
* {@inheritdoc}
*
* @covers ::__construct
*/
protected function setUp() {
$this->cache = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
$this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
$this->elementInfo = new ElementInfoManager(new \ArrayObject(), $this->cache, $this->moduleHandler);
}
/**
* Tests the getInfo method.
*
* @covers ::getInfo
* @covers ::buildInfo
*
* @dataProvider providerTestGetInfo
*/
public function testGetInfo($type, $expected_info, $element_info, callable $alter_callback = NULL) {
$this->moduleHandler->expects($this->once())
->method('invokeAll')
->with('element_info')
->will($this->returnValue($element_info));
$this->moduleHandler->expects($this->once())
->method('alter')
->with('element_info', $this->anything())
->will($this->returnCallback($alter_callback ?: function($info) {
return $info;
}));
$this->assertEquals($expected_info, $this->elementInfo->getInfo($type));
}
/**
* Provides tests data for getInfo.
*
* @return array
*/
public function providerTestGetInfo() {
$data = array();
// Provide an element and expect it is returned.
$data[] = array(
'page',
array(
'#type' => 'page',
'#show_messages' => TRUE,
'#theme' => 'page',
),
array('page' => array(
'#show_messages' => TRUE,
'#theme' => 'page',
)),
);
// Provide an element but request an non existent one.
$data[] = array(
'form',
array(
),
array('page' => array(
'#show_messages' => TRUE,
'#theme' => 'page',
)),
);
// Provide an element and alter it to ensure it is altered.
$data[] = array(
'page',
array(
'#type' => 'page',
'#show_messages' => TRUE,
'#theme' => 'page',
'#number' => 597219,
),
array('page' => array(
'#show_messages' => TRUE,
'#theme' => 'page',
)),
function ($alter_name, array &$info) {
$info['page']['#number'] = 597219;
}
);
return $data;
}
/**
* Tests the getInfo() method when render element plugins are used.
*
* @covers ::getInfo
* @covers ::buildInfo
*
* @dataProvider providerTestGetInfoElementPlugin
*/
public function testGetInfoElementPlugin($plugin_class, $expected_info) {
$this->moduleHandler->expects($this->once())
->method('invokeAll')
->with('element_info')
->willReturn(array());
$this->moduleHandler->expects($this->once())
->method('alter')
->with('element_info', $this->anything())
->will($this->returnArgument(0));
$plugin = $this->getMock($plugin_class);
$plugin->expects($this->once())
->method('getInfo')
->willReturn(array(
'#show_messages' => TRUE,
'#theme' => 'page',
));
$element_info = $this->getMockBuilder('Drupal\Core\Render\ElementInfoManager')
->setConstructorArgs(array(new \ArrayObject(), $this->cache, $this->moduleHandler))
->setMethods(array('getDefinitions', 'createInstance'))
->getMock();
$element_info->expects($this->once())
->method('createInstance')
->with('page')
->willReturn($plugin);
$element_info->expects($this->once())
->method('getDefinitions')
->willReturn(array(
'page' => array('class' => 'TestElementPlugin'),
));
$this->assertEquals($expected_info, $element_info->getInfo('page'));
}
/**
* Provides tests data for testGetInfoElementPlugin().
*
* @return array
*/
public function providerTestGetInfoElementPlugin() {
$data = array();
$data[] = array(
'Drupal\Core\Render\Element\ElementInterface',
array(
'#type' => 'page',
'#show_messages' => TRUE,
'#theme' => 'page',
),
);
$data[] = array(
'Drupal\Core\Render\Element\FormElementInterface',
array(
'#type' => 'page',
'#show_messages' => TRUE,
'#theme' => 'page',
'#input' => TRUE,
'#value_callback' => array('TestElementPlugin', 'valueCallback'),
),
);
return $data;
}
}

View File

@ -1,118 +0,0 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Render\ElementInfoTest.
*/
namespace Drupal\Tests\Core\Render;
use Drupal\Core\Render\ElementInfo;
use Drupal\Core\Render\ElementInfoInterface;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\Core\Render\ElementInfo
* @group Render
*/
class ElementInfoTest extends UnitTestCase {
/**
* The class under test.
*
* @var \Drupal\Core\Render\ElementInfoInterface
*/
protected $elementInfo;
/**
* The mocked module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $moduleHandler;
/**
* {@inheritdoc}
*
* @covers ::__construct
*/
protected function setUp() {
$this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
$this->elementInfo = new ElementInfo($this->moduleHandler);
}
/**
* Tests the getInfo method.
*
* @covers ::getInfo
* @covers ::buildInfo
*
* @dataProvider providerTestGetInfo
*/
public function testGetInfo($type, $expected_info, $element_info, callable $alter_callback = NULL) {
$this->moduleHandler->expects($this->once())
->method('invokeAll')
->with('element_info')
->will($this->returnValue($element_info));
$this->moduleHandler->expects($this->once())
->method('alter')
->with('element_info', $this->anything())
->will($this->returnCallback($alter_callback ?: function($info) {
return $info;
}));
$this->assertEquals($expected_info, $this->elementInfo->getInfo($type));
}
/**
* Provides tests data for getInfo.
*
* @return array
*/
public function providerTestGetInfo() {
$data = array();
// Provide an element and expect it is returned.
$data[] = array(
'page',
array(
'#type' => 'page',
'#show_messages' => TRUE,
'#theme' => 'page',
),
array('page' => array(
'#show_messages' => TRUE,
'#theme' => 'page',
)),
);
// Provide an element but request an non existent one.
$data[] = array(
'form',
array(
),
array('page' => array(
'#show_messages' => TRUE,
'#theme' => 'page',
)),
);
// Provide an element and alter it to ensure it is altered.
$data[] = array(
'page',
array(
'#type' => 'page',
'#show_messages' => TRUE,
'#theme' => 'page',
'#number' => 597219,
),
array('page' => array(
'#show_messages' => TRUE,
'#theme' => 'page',
)),
function ($alter_name, array &$info) {
$info['page']['#number'] = 597219;
}
);
return $data;
}
}