diff --git a/core/includes/form.inc b/core/includes/form.inc index 571a439..2e7436f 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -766,7 +766,14 @@ function drupal_retrieve_form($form_id, &$form_state) { } if (isset($form_definition['callback'])) { $callback = $form_definition['callback']; - $form_state['build_info']['base_form_id'] = $callback; + if (empty($form_definition['base_form_ids'])) { + $form_definition['base_form_ids'] = array($callback); + } + // The most specific base form id will be used as the main one. + $form_state['build_info']['base_form_id'] = end($form_definition['base_form_ids']); + // Store the additional base form ids to allow for more generic or + // granular form altering. + $form_state['build_info']['base_form_ids'] = $form_definition['base_form_ids']; } // In case $form_state['wrapper_callback'] is not defined already, we also // allow hook_forms() to define one. @@ -1085,8 +1092,10 @@ function drupal_prepare_form($form_id, &$form, &$form_state) { // Invoke hook_form_alter(), hook_form_BASE_FORM_ID_alter(), and // hook_form_FORM_ID_alter() implementations. $hooks = array('form'); - if (isset($form_state['build_info']['base_form_id'])) { - $hooks[] = 'form_' . $form_state['build_info']['base_form_id']; + if (isset($form_state['build_info']['base_form_ids'])) { + foreach ($form_state['build_info']['base_form_ids'] as $base_form_id) { + $hooks[] = 'form_' . $base_form_id; + } } $hooks[] = 'form_' . $form_id; drupal_alter($hooks, $form, $form_state, $form_id); @@ -1465,7 +1474,7 @@ function form_execute_handlers($type, &$form, &$form_state) { $batch['has_form_submits'] = TRUE; } else { - $function($form, $form_state); + call_user_func_array($function, array($form, &$form_state)); } $return = TRUE; } diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module index a6230f2..31f2f6c 100644 --- a/core/modules/comment/comment.module +++ b/core/modules/comment/comment.module @@ -103,8 +103,11 @@ function comment_entity_info() { 'base table' => 'comment', 'uri callback' => 'comment_uri', 'fieldable' => TRUE, - 'controller class' => 'Drupal\comment\CommentStorageController', 'entity class' => 'Drupal\comment\Comment', + 'controller class' => 'Drupal\comment\CommentStorageController', + 'form controller class' => array( + 'default' => 'Drupal\comment\CommentFormController', + ), 'entity keys' => array( 'id' => 'cid', 'bundle' => 'node_type', @@ -765,8 +768,7 @@ function comment_node_page_additions(Node $node) { // Append comment form if needed. if (user_access('post comments') && $node->comment == COMMENT_NODE_OPEN && (variable_get('comment_form_location_' . $node->type, COMMENT_FORM_BELOW) == COMMENT_FORM_BELOW)) { - $comment = entity_create('comment', array('nid' => $node->nid)); - $additions['comment_form'] = drupal_get_form("comment_node_{$node->type}_form", $comment); + $additions['comment_form'] = comment_edit($node); } if ($additions) { @@ -782,6 +784,14 @@ function comment_node_page_additions(Node $node) { } /** + * Returns a rendered form to comment the given node. + */ +function comment_edit($node, $pid = NULL) { + $values = array('nid' => $node->nid, 'pid' => $pid, 'node_type' => 'comment_node_' . $node->type); + return entity_create('comment', $values)->form(); +} + +/** * Retrieves comments for a thread. * * @param Drupal\node\Node $node @@ -1650,19 +1660,7 @@ function comment_get_display_page($cid, $node_type) { */ function comment_edit_page(Comment $comment) { drupal_set_title(t('Edit comment %comment', array('%comment' => $comment->subject)), PASS_THROUGH); - $node = node_load($comment->nid); - return drupal_get_form("comment_node_{$node->type}_form", $comment); -} - -/** - * Implements hook_forms(). - */ -function comment_forms() { - $forms = array(); - foreach (node_type_get_types() as $type) { - $forms["comment_node_{$type->type}_form"]['callback'] = 'comment_form'; - } - return $forms; + return $comment->form(); } /** @@ -1688,7 +1686,9 @@ function comment_form($form, &$form_state, Comment $comment) { } $node = node_load($comment->nid); + // @todo Remove all the usages of $form['#node']. $form['#node'] = $node; + $form_state['node'] = $node; // Use #comment-form as unique jump target, regardless of node type. $form['#id'] = drupal_html_id('comment_form'); @@ -1848,24 +1848,8 @@ function comment_form($form, &$form_state, Comment $comment) { '#value' => $comment_langcode, ); - // Only show the save button if comment previews are optional or if we are - // already previewing the submission. - $form['actions'] = array('#type' => 'actions'); - $form['actions']['submit'] = array( - '#type' => 'submit', - '#value' => t('Save'), - '#access' => ($comment->cid && user_access('administer comments')) || variable_get('comment_preview_' . $node->type, DRUPAL_OPTIONAL) != DRUPAL_REQUIRED || isset($form_state['comment_preview']), - ); - $form['actions']['preview'] = array( - '#type' => 'submit', - '#value' => t('Preview'), - '#access' => (variable_get('comment_preview_' . $node->type, DRUPAL_OPTIONAL) != DRUPAL_DISABLED), - '#submit' => array('comment_form_build_preview'), - ); - // Attach fields. $comment->node_type = 'comment_node_' . $node->type; - field_attach_form('comment', $comment, $form, $form_state); return $form; } @@ -1948,8 +1932,6 @@ function comment_preview(Comment $comment) { function comment_form_validate($form, &$form_state) { global $user; - entity_form_field_validate('comment', $form, $form_state); - if (!empty($form_state['values']['cid'])) { // Verify the name in case it is being changed from being anonymous. $account = user_load_by_name($form_state['values']['name']); diff --git a/core/modules/comment/comment.pages.inc b/core/modules/comment/comment.pages.inc index 5176846..7ee3e04 100644 --- a/core/modules/comment/comment.pages.inc +++ b/core/modules/comment/comment.pages.inc @@ -43,8 +43,7 @@ function comment_reply(Node $node, $pid = NULL) { // The user is previewing a comment prior to submitting it. if ($op == t('Preview')) { if (user_access('post comments')) { - $comment = entity_create('comment', array('nid' => $node->nid, 'pid' => $pid)); - $build['comment_form'] = drupal_get_form("comment_node_{$node->type}_form", $comment); + $build['comment_form'] = comment_edit($node, $pid); } else { drupal_set_message(t('You are not authorized to post comments.'), 'error'); @@ -92,8 +91,7 @@ function comment_reply(Node $node, $pid = NULL) { drupal_goto("node/$node->nid"); } elseif (user_access('post comments')) { - $comment = entity_create('comment', array('nid' => $node->nid, 'pid' => $pid)); - $build['comment_form'] = drupal_get_form("comment_node_{$node->type}_form", $comment); + $build['comment_form'] = comment_edit($node, $pid); } else { drupal_set_message(t('You are not authorized to post comments.'), 'error'); diff --git a/core/modules/comment/lib/Drupal/comment/CommentFormController.php b/core/modules/comment/lib/Drupal/comment/CommentFormController.php new file mode 100644 index 0000000..6f61634 --- /dev/null +++ b/core/modules/comment/lib/Drupal/comment/CommentFormController.php @@ -0,0 +1,69 @@ +type, DRUPAL_OPTIONAL); + + // No delete action on the comment form. + unset($element['delete']); + + // Only show the save button if comment previews are optional or if we are + // already previewing the submission. + $element['submit']['#access'] = ($comment->cid && user_access('administer comments')) || $preview_mode != DRUPAL_REQUIRED || isset($form_state['comment_preview']); + + $element['preview'] = array( + '#type' => 'submit', + '#value' => t('Preview'), + '#access' => $preview_mode != DRUPAL_DISABLED, + '#submit' => array('comment_form_build_preview'), + ); + + $element['#weight'] = $form['comment_body']['#weight'] + 0.01; + + return $element; + } + + /** + * @see Drupal\entity\EntityFormController::validate() + */ + public function validate(array $form, array &$form_state) { + parent::validate($form, $form_state); + comment_form_validate($form, $form_state); + } + + /** + * @see Drupal\entity\EntityFormController::save() + */ + public function save(array $form, array &$form_state) { + // Completely override the base method as we need to perform some advanced + // tweak before regularly building the entity from the submitted values. + comment_form_submit($form, $form_state); + } +} diff --git a/core/modules/entity/entity.api.php b/core/modules/entity/entity.api.php index 02d8754..4d4830b 100644 --- a/core/modules/entity/entity.api.php +++ b/core/modules/entity/entity.api.php @@ -27,6 +27,12 @@ * The class has to implement the * Drupal\entity\EntityStorageControllerInterface interface. Leave blank * to use the Drupal\entity\DatabaseStorageController implementation. + * - form controller class: An array of form controller class names keyed by + * context name. Each class handle the edit form for the entity being + * defined, alongside with its handlers, for the related context. The + * context is also passed to the class costructor hence if only small tweaks + * are needed to adapt the edit form to the various contexts a unique class + * may be provided. * - base table: (used by Drupal\entity\DatabaseStorageController) The * name of the entity type's base table. * - static cache: (used by Drupal\entity\DatabaseStorageController) @@ -136,6 +142,9 @@ function hook_entity_info() { 'label' => t('Node'), 'entity class' => 'Drupal\node\Node', 'controller class' => 'Drupal\node\NodeStorageController', + 'form controller class' => array( + 'default' => 'Drupal\node\NodeFormController', + ), 'base table' => 'node', 'revision table' => 'node_revision', 'uri callback' => 'node_uri', diff --git a/core/modules/entity/entity.module b/core/modules/entity/entity.module index a66b1e8..37819e4 100644 --- a/core/modules/entity/entity.module +++ b/core/modules/entity/entity.module @@ -5,8 +5,8 @@ * Entity API for handling entities like nodes or users. */ -use Drupal\entity\EntityMalformedException; use Drupal\entity\EntityInterface; +use Drupal\entity\EntityMalformedException; /** * Implements hook_help(). @@ -71,6 +71,9 @@ function entity_get_info($entity_type = NULL) { 'fieldable' => FALSE, 'entity class' => 'Drupal\entity\Entity', 'controller class' => 'Drupal\entity\DatabaseStorageController', + 'form controller class' => array( + 'default' => 'Drupal\entity\EntityFormController', + ), 'static cache' => TRUE, 'field cache' => TRUE, 'bundles' => array(), @@ -447,6 +450,120 @@ function entity_form_field_validate($entity_type, $form, &$form_state) { } /** + * Returns a form controller for the given context. + * + * Since there might be different contexts in which an entity or parts of it are + * edited, multiple form controllers suitable to the different contexts may be + * defined. If no valid controller is found for the given context the default + * one will be used. + * + * @param $entity_type + * The type of the entity. + * @param + * (optional) The bundle of the entity being edited. + * @param $context + * (optional) The name of the context identifying the form controller. + * + * @return Drupal\entity\EntityFormController + * An instance of the Drupal\Core\Entity\FormController class. + */ +function entity_get_form_controller($entity_type, $bundle = NULL, $context = 'default') { + $info = entity_get_info($entity_type); + + // If no controller is specified default to the base implementation. + if (!empty($info) && empty($info['form controller class'])) { + $class = 'Drupal\entity\EntityFormController'; + } + + // Check whether a bundle-specific form controller exists. + if (!empty($bundle) && !empty($info['bundle form controller class'][$bundle][$context])) { + $class = $info['bundle form controller class'][$bundle][$context]; + } + // Fall back to the per-entity-type form controller. + elseif (!empty($info['form controller class'][$context])) { + $class = $info['form controller class'][$context]; + } + // If a non-existing context has been specified stop. + else { + // @todo Here we should throw an exception. + return FALSE; + } + + return new $class($context); +} + +/** + * Helper function. Returns the form id for the given entity. + */ +function _entity_form_id(EntityInterface $entity, $context = 'default') { + $entity_type = $entity->entityType(); + $bundle = $entity->bundle(); + $form_id = $entity_type; + if ($bundle != $entity_type) { + $form_id = $bundle . '_' . $form_id; + } + if ($context != 'default') { + $form_id = $form_id . '_' . $context; + } + return $form_id . '_form'; +} + +/** + * Returns a rendered edit form for the given entity and context. + * + * @param EntityInterface|string $entity + * The entity being edited or the entity type to be created. + * @param $context + * (optional) The context for the form to be returned. + * + * @return + * A rendered edit form for the given entity. + */ +function entity_get_form($entity, $context = 'default') { + if (is_string($entity)) { + $entity = entity_create($entity, array()); + } + $form_id = _entity_form_id($entity, $context); + return drupal_get_form($form_id, $entity, $context); +} + +/** + * Implements hook_forms(). + * + * Returns the generic 'entity_form' callback and defines the additional + * ENTITY_TYPE_form base id to allow for both generic and entity-specific form + * alterations. This we have three levels of granularity: bundle, entity type, + * all. + */ +function entity_forms($form_id, $args) { + $forms = array(); + + if (!empty($args) && $args[0] instanceof EntityInterface) { + try { + list($entity, $context) = $args; + if ($form_id == _entity_form_id($entity, $context)) { + $callback = 'entity_form'; + $forms[$form_id]['callback'] = $callback; + $forms[$form_id]['base_form_ids'] = array($callback, $entity->entityType() . '_form'); + } + } + catch (EntityMalformedException $e) { + // Not an entity form, exit silently. + } + } + + return $forms; +} + +/** + * Form builder for any entity form. + */ +function entity_form($form, &$form_state, EntityInterface $entity, $context) { + $controller = entity_get_form_controller($entity->entityType(), $entity->bundle(), $context); + return $controller->build($form, $form_state, $entity); +} + +/** * Copies submitted values to entity properties for simple entity forms. * * During the submission handling of an entity form's "Save", "Preview", and diff --git a/core/modules/entity/lib/Drupal/entity/Entity.php b/core/modules/entity/lib/Drupal/entity/Entity.php index eeaeee8..5b47228 100644 --- a/core/modules/entity/lib/Drupal/entity/Entity.php +++ b/core/modules/entity/lib/Drupal/entity/Entity.php @@ -270,4 +270,13 @@ class Entity implements EntityInterface { return $this->isCurrentRevision; } + /** + * Implements EntityInterface::form(). + * + * @param $context + * (optional) The context for the form to be returned. + */ + public function form($context = 'default') { + return entity_get_form($this, $context); + } } diff --git a/core/modules/entity/lib/Drupal/entity/EntityFormController.php b/core/modules/entity/lib/Drupal/entity/EntityFormController.php new file mode 100644 index 0000000..15fc664 --- /dev/null +++ b/core/modules/entity/lib/Drupal/entity/EntityFormController.php @@ -0,0 +1,285 @@ +context = $context; + } + + /** + * Builds an entity edit form. + * + * @param $form + * An associative array containing the structure of the form. + * @param $form_state + * A reference to a keyed array containing the current state of the form. + * @param $entity_type + * The type of the entity being edited. + * @param $entity + * The entity being edited. + * + * @return + * The array containing the complete form. + */ + public final function build(array $form, array &$form_state, EntityInterface $entity) { + // Set the entity form controller in the form state. + EntityFormController::setFormInstance($form_state, $this); + + // During initial form build, add the entity to the form state for use + // during form building and processing. During a rebuild, use what is in the + // form state. + $form_entity = EntityFormController::getEntity($form_state); + if (empty($form_entity)) { + EntityFormController::setEntity($form_state, $entity); + } + else { + $entity = $form_entity; + } + + // Retrieve the form array. + $form = $this->form($form, $form_state, $entity); + + // Retrieve the form actions. + $actions = $this->actionsElement($form, $form_state, $entity); + if (!empty($actions)) { + $form['actions'] = $actions; + } + + return $form; + } + + /** + * Returns the actual form array to be built. + * + * @see FormController::build(). + */ + protected function form(array $form, array &$form_state, EntityInterface $entity) { + // @todo Exploit the Property API to generate the default widgets for the + // entity properties. + $info = $entity->entityInfo(); + if (!empty($info['fieldable'])) { + field_attach_form($entity->entityType(), $entity, $form, $form_state, $this->getFormLangcode($form_state)); + } + return $form; + } + + /** + * Returns the action form element for the current entity form. + */ + protected final function actionsElement(array $form, array &$form_state, EntityInterface $entity) { + $element = $this->actions($form, $form_state, $entity); + + // We cannot delete an entity that has not been created yet. + if (!$entity->id()) { + unset($element['delete']); + } + elseif (isset($element['delete'])) { + // Move the delete action as last one, unless weights are explictly + // provided. + $delete = $element['delete']; + unset($element['delete']); + $element['delete'] = $delete; + } + + $count = 0; + $submit = array($this, 'submit'); + + foreach (element_children($element) as $action) { + $element[$action] += array( + '#type' => 'submit', + '#weight' => ++$count * 5, + '#validate' => array(), + '#submit' => array(), + ); + // Ensure we always preprocess submitted data before calling the actual + // submission handlers. + array_unshift($element[$action]['#submit'], $submit); + } + + if (!empty($element)) { + $element['#type'] = 'actions'; + } + + return $element; + } + + /** + * Returns an array of supported actions for the current entity form. + */ + protected function actions(array $form, array &$form_state, EntityInterface $entity) { + return array( + 'submit' => array( + '#value' => t('Save'), + '#validate' => array(array($this, 'validate')), + '#submit' => array(array($this, 'save')), + ), + 'delete' => array( + '#value' => t('Delete'), + // No need to validate the form when deleting the entity. + '#submit' => array(array($this, 'delete')), + ), + ); + } + + /** + * Validates the submitted form values. + * + * @param $form + * An associative array containing the structure of the form. + * @param $form_state + * A reference to a keyed array containing the current state of the form. + */ + public function validate(array $form, array &$form_state) { + // @todo Exploit the Property API to validate the values submitted for the + // entity properties. + $entity = EntityFormController::getEntity($form_state); + $info = $entity->entityInfo(); + if (!empty($info['fieldable'])) { + entity_form_field_validate($entity->entityType(), $form, $form_state); + } + + // @todo Remove this. + // Execute legacy global validation handlers. + unset($form_state['validate_handlers']); + form_execute_handlers('validate', $form, $form_state); + } + + /** + * Processes the submitted form values and updates the entity object. + * + * @param $form + * An associative array containing the structure of the form. + * @param $form_state + * A reference to a keyed array containing the current state of the form. + */ + public function submit(array $form, array &$form_state) { + // @todo Exploit the Property API to process the submitted entity property + // values. + $entity = EntityFormController::getEntity($form_state); + // @todo Do we really need this? + $this->prebuildEntity($form, $form_state); + // @todo Make entity_form_submit_build_entity() an entity form controller + // method. + entity_form_submit_build_entity($entity->entityType(), $entity, $form, $form_state); + EntityFormController::setEntity($form_state, $entity); + } + + /** + * Gives a chance to manipulate submitted data before building the entity. + * + * @param $form + * An associative array containing the structure of the form. + * @param $form_state + * A reference to a keyed array containing the current state of the form. + */ + protected function prebuildEntity(array $form, array &$form_state) { + if (isset($form['#entity_prebuilders'])) { + foreach ($form['#entity_prebuilders'] as $function) { + $function($form, $form_state); + } + } + } + + /** + * Submit handler for the 'save' action. + * + * @param $form + * An associative array containing the structure of the form. + * @param $form_state + * A reference to a keyed array containing the current state of the form. + */ + public function save(array $form, array &$form_state) {} + + /** + * Submit handler for the 'delete' action. + * + * @param array $form + * @param array $form_state + */ + public function delete(array $form, array &$form_state) {} + + /** + * Returns the code identifying the active form language. + */ + public function getFormLangcode($form_state) { + // @todo Introduce a new language type to use as the default language. + $language = EntityFormController::getEntity($form_state)->language(); + return !empty($language->langcode) ? $language->langcode : NULL; + } + + /** + * Returns the entity being edited. + * + * @param $form_state + * The current form state. + * + * @return Drupal\entity\Entity + * The entity being edited in the current form. + */ + public static final function getEntity($form_state) { + return isset($form_state['entity']) ? $form_state['entity'] : FALSE; + } + + /** + * Stores the entity being edited. + * + * @param $form_state + * The current form state. + * @param EntityInterface $entity + * The entity being edited in the current form. + */ + protected static final function setEntity(&$form_state, EntityInterface $entity) { + return $form_state['entity'] = $entity; + } + + /** + * Returns an instance of the entity form controller. + * + * @param $form_state + * The current form state. + * @return Drupal\entity\EntityFormController + * An instance of the entity form controller associated with the current + * form. + */ + protected static final function getFormInstance($form_state) { + return isset($form_state['entity_form_controller']) ? $form_state['entity_form_controller'] : FALSE; + } + + /** + * Associates the current form controller to the form being edited. + * + * @param $form_state + * The current form state. + * @param EntityFormController $controller + * The current entity form controller. + */ + protected static final function setFormInstance(&$form_state, EntityFormController $controller) { + $form_state['entity_form_controller'] = $controller; + } +} diff --git a/core/modules/entity/lib/Drupal/entity/EntityInterface.php b/core/modules/entity/lib/Drupal/entity/EntityInterface.php index 699c424..2479c28 100644 --- a/core/modules/entity/lib/Drupal/entity/EntityInterface.php +++ b/core/modules/entity/lib/Drupal/entity/EntityInterface.php @@ -205,4 +205,10 @@ interface EntityInterface { */ public function isCurrentRevision(); + /** + * Returns a rendered entity edit form. + * + * @param $context + */ + public function form($context = 'default'); } diff --git a/core/modules/node/lib/Drupal/node/NodeFormController.php b/core/modules/node/lib/Drupal/node/NodeFormController.php new file mode 100644 index 0000000..f1c9a30 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/NodeFormController.php @@ -0,0 +1,76 @@ +type, DRUPAL_OPTIONAL); + + $element['preview'] = array( + '#access' => $preview_mode != DRUPAL_DISABLED, + '#value' => t('Preview'), + '#submit' => array('node_form_build_preview'), + ); + + $element['submit']['#access'] = $preview_mode != DRUPAL_REQUIRED || (!form_get_errors() && isset($form_state['node_preview'])); + $element['delete']['#access'] = node_access('delete', $node); + + return $element; + } + + /** + * @see Drupal\entity\EntityFormController::validate() + */ + public function validate(array $form, array &$form_state) { + node_form_validate($form, $form_state); + parent::validate($form, $form_state); + } + + /** + * @see Drupal\entity\EntityFormController::submit() + */ + public function submit(array $form, array &$form_state) { + // Handle possible field translations first and then build the node from the + // submitted values. + node_field_language_form_submit($form, $form_state); + parent::submit($form, $form_state); + } + + /** + * @see Drupal\entity\EntityFormController::save() + */ + public function save(array $form, array &$form_state) { + node_form_submit($form, $form_state); + } + + /** + * @see Drupal\entity.EntityFormController::delete() + */ + public function delete(array $form, array &$form_state) { + node_form_delete_submit($form, $form_state); + } +} diff --git a/core/modules/node/node.module b/core/modules/node/node.module index 42d968d..10022b2 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -193,8 +193,11 @@ function node_entity_info() { $return = array( 'node' => array( 'label' => t('Node'), - 'controller class' => 'Drupal\node\NodeStorageController', 'entity class' => 'Drupal\node\Node', + 'controller class' => 'Drupal\node\NodeStorageController', + 'form controller class' => array( + 'default' => 'Drupal\node\NodeFormController', + ), 'base table' => 'node', 'revision table' => 'node_revision', 'uri callback' => 'node_uri', @@ -3632,21 +3635,6 @@ function node_content_form(Node $node, $form_state) { */ /** - * Implements hook_forms(). - * - * All node forms share the same form handler. - */ -function node_forms() { - $forms = array(); - if ($types = node_type_get_types()) { - foreach (array_keys($types) as $type) { - $forms[$type . '_node_form']['callback'] = 'node_form'; - } - } - return $forms; -} - -/** * Implements hook_action_info(). */ function node_action_info() { diff --git a/core/modules/node/node.pages.inc b/core/modules/node/node.pages.inc index faa3dea..a319fb8 100644 --- a/core/modules/node/node.pages.inc +++ b/core/modules/node/node.pages.inc @@ -9,6 +9,8 @@ * @see node_menu() */ +use Drupal\entity\EntityFormController; + use Drupal\node\Node; /** @@ -19,7 +21,7 @@ use Drupal\node\Node; function node_page_edit($node) { $type_name = node_type_get_name($node); drupal_set_title(t('Edit @type @title', array('@type' => $type_name, '@title' => $node->title)), PASS_THROUGH); - return drupal_get_form($node->type . '_node_form', $node); + return $node->form(); } /** @@ -94,7 +96,7 @@ function node_add($type) { 'langcode' => node_type_get_default_langcode($type) )); drupal_set_title(t('Create @name', array('@name' => $types[$type]->name)), PASS_THROUGH); - $output = drupal_get_form($type . '_node_form', $node); + $output = $node->form(); return $output; } @@ -111,12 +113,11 @@ function node_form_validate($form, &$form_state) { // $form_state['node'] contains the actual entity being edited, but we must // not update it with form values that have not yet been validated, so we // create a pseudo-entity to use during validation. - $node = clone $form_state['node']; + $node = clone EntityFormController::getEntity($form_state); foreach ($form_state['values'] as $key => $value) { $node->{$key} = $value; } node_validate($node, $form, $form_state); - entity_form_field_validate('node', $form, $form_state); } /** @@ -326,43 +327,13 @@ function node_form($form, &$form_state, Node $node) { '#default_value' => $node->sticky, ); - // Add the buttons. - $form['actions'] = array('#type' => 'actions'); - $form['actions']['submit'] = array( - '#type' => 'submit', - '#access' => variable_get('node_preview_' . $node->type, DRUPAL_OPTIONAL) != DRUPAL_REQUIRED || (!form_get_errors() && isset($form_state['node_preview'])), - '#value' => t('Save'), - '#weight' => 5, - '#submit' => array('node_form_submit'), - ); - $form['actions']['preview'] = array( - '#access' => variable_get('node_preview_' . $node->type, DRUPAL_OPTIONAL) != DRUPAL_DISABLED, - '#type' => 'submit', - '#value' => t('Preview'), - '#weight' => 10, - '#submit' => array('node_form_build_preview'), - ); - if (!empty($node->nid) && node_access('delete', $node)) { - $form['actions']['delete'] = array( - '#type' => 'submit', - '#value' => t('Delete'), - '#weight' => 15, - '#submit' => array('node_form_delete_submit'), - ); - } // This form uses a button-level #submit handler for the form's main submit // action. node_form_submit() manually invokes all form-level #submit handlers // of the form. Without explicitly setting #submit, Form API would auto-detect // node_form_submit() as submit handler, but that is the button-level #submit - // handler for the 'Save' action. To maintain backwards compatibility, a - // #submit handler is auto-suggested for custom node type modules. - $form['#validate'][] = 'node_form_validate'; - if (!isset($form['#submit']) && function_exists($node->type . '_node_form_submit')) { - $form['#submit'][] = $node->type . '_node_form_submit'; - } + // handler for the 'Save' action. $form += array('#submit' => array()); - field_attach_form('node', $node, $form, $form_state, $node->langcode); return $form; } @@ -380,7 +351,7 @@ function node_form_delete_submit($form, &$form_state) { $destination = drupal_get_destination(); unset($_GET['destination']); } - $node = $form['#node']; + $node = EntityFormController::getEntity($form_state); $form_state['redirect'] = array('node/' . $node->nid . '/delete', array('query' => $destination)); } @@ -492,9 +463,6 @@ function theme_node_preview($variables) { * @see node_form_submit_build_node() */ function node_form_submit($form, &$form_state) { - // Handle possible field translations first and then build the node from the - // submitted values. - node_field_language_form_submit($form, $form_state); $node = node_form_submit_build_node($form, $form_state); $insert = empty($node->nid); $node->save(); @@ -565,19 +533,7 @@ function node_field_language_form_submit($form, &$form_state) { * @see node_form_submit() */ function node_form_submit_build_node($form, &$form_state) { - // @todo Legacy support for modules that extend the node form with form-level - // submit handlers that adjust $form_state['values'] prior to those values - // being used to update the entity. Module authors are encouraged to instead - // adjust the node directly within a hook_node_submit() implementation. For - // Drupal 8, evaluate whether the pattern of triggering form-level submit - // handlers during button-level submit processing is worth supporting - // properly, and if so, add a Form API function for doing so. - unset($form_state['submit_handlers']); - form_execute_handlers('submit', $form, $form_state); - - $node = $form_state['node']; - entity_form_submit_build_entity('node', $node, $form, $form_state); - + $node = EntityFormController::getEntity($form_state); node_submit($node); foreach (module_implements('node_submit') as $module) { $function = $module . '_node_submit'; diff --git a/core/modules/poll/lib/Drupal/poll/PollFormController.php b/core/modules/poll/lib/Drupal/poll/PollFormController.php new file mode 100644 index 0000000..0ab5a2f --- /dev/null +++ b/core/modules/poll/lib/Drupal/poll/PollFormController.php @@ -0,0 +1,18 @@ + '0', ); - $form['actions'] = array('#type' => 'actions'); - $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save')); if (isset($vocabulary->vid)) { - $form['actions']['delete'] = array('#type' => 'submit', '#value' => t('Delete')); $form['vid'] = array('#type' => 'value', '#value' => $vocabulary->vid); } - $form['#validate'][] = 'taxonomy_form_vocabulary_validate'; return $form; } @@ -207,15 +204,7 @@ function taxonomy_form_vocabulary_validate($form, &$form_state) { * @see taxonomy_form_vocabulary_validate() */ function taxonomy_form_vocabulary_submit($form, &$form_state) { - if ($form_state['triggering_element']['#value'] == t('Delete')) { - // Rebuild the form to confirm vocabulary deletion. - $form_state['rebuild'] = TRUE; - $form_state['confirm_delete'] = TRUE; - return; - } - - $vocabulary = $form_state['vocabulary']; - entity_form_submit_build_entity('taxonomy_vocabulary', $vocabulary, $form, $form_state); + $vocabulary = EntityFormController::getEntity($form_state); // Prevent leading and trailing spaces in vocabulary names. $vocabulary->name = trim($vocabulary->name); @@ -643,6 +632,14 @@ function theme_taxonomy_overview_terms($variables) { } /** + * Returns a rendered edit form to create a new term associated to the given vocabulary. + */ +function taxonomy_term_add($vocabulary) { + $term = entity_create('taxonomy_term', array('vid' => $vocabulary->vid, 'vocabulary_machine_name' => $vocabulary->machine_name)); + return $term->form(); +} + +/** * Form function for the term edit form. * * @param Drupal\taxonomy\Term|null $term @@ -710,8 +707,6 @@ function taxonomy_form_term($form, &$form_state, Term $term = NULL, Vocabulary $ '#value' => isset($term->vocabulary_machine_name) ? $term->vocabulary_machine_name : $vocabulary->name, ); - field_attach_form('taxonomy_term', $term, $form, $form_state); - $form['relations'] = array( '#type' => 'fieldset', '#title' => t('Relations'), @@ -770,23 +765,7 @@ function taxonomy_form_term($form, &$form_state, Term $term = NULL, Vocabulary $ '#value' => $term->tid, ); - $form['actions'] = array('#type' => 'actions'); - $form['actions']['submit'] = array( - '#type' => 'submit', - '#value' => t('Save'), - '#weight' => 5, - ); - - if ($term->tid) { - $form['actions']['delete'] = array( - '#type' => 'submit', - '#value' => t('Delete'), - '#access' => taxonomy_term_access('delete', $term), - '#weight' => 10, - '#submit' => array('taxonomy_form_term_delete_submit'), - ); - } - else { + if (empty($term->tid)) { $form_state['redirect'] = current_path(); } @@ -799,8 +778,6 @@ function taxonomy_form_term($form, &$form_state, Term $term = NULL, Vocabulary $ * @see taxonomy_form_term() */ function taxonomy_form_term_validate($form, &$form_state) { - entity_form_field_validate('taxonomy_term', $form, $form_state); - // Ensure numeric values. if (isset($form_state['values']['weight']) && !is_numeric($form_state['values']['weight'])) { form_set_error('weight', t('Weight value must be numeric.')); @@ -813,7 +790,7 @@ function taxonomy_form_term_validate($form, &$form_state) { * @see taxonomy_form_term() */ function taxonomy_form_term_submit($form, &$form_state) { - $term = taxonomy_form_term_submit_build_taxonomy_term($form, $form_state); + $term = EntityFormController::getEntity($form_state); $status = taxonomy_term_save($term); switch ($status) { @@ -857,8 +834,7 @@ function taxonomy_form_term_submit($form, &$form_state) { * Updates the form state's term entity by processing this submission's values. */ function taxonomy_form_term_submit_build_taxonomy_term($form, &$form_state) { - $term = $form_state['term']; - entity_form_submit_build_entity('taxonomy_term', $term, $form, $form_state); + $term = EntityFormController::getEntity($form_state); // Prevent leading and trailing spaces in term names. $term->name = trim($term->name); diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module index 6697a0a..215380e 100644 --- a/core/modules/taxonomy/taxonomy.module +++ b/core/modules/taxonomy/taxonomy.module @@ -113,6 +113,9 @@ function taxonomy_entity_info() { 'label' => t('Taxonomy term'), 'entity class' => 'Drupal\taxonomy\Term', 'controller class' => 'Drupal\taxonomy\TermStorageController', + 'form controller class' => array( + 'default' => 'Drupal\taxonomy\TermFormController', + ), 'base table' => 'taxonomy_term_data', 'uri callback' => 'taxonomy_term_uri', 'fieldable' => TRUE, @@ -149,6 +152,9 @@ function taxonomy_entity_info() { 'label' => t('Taxonomy vocabulary'), 'entity class' => 'Drupal\taxonomy\Vocabulary', 'controller class' => 'Drupal\taxonomy\VocabularyStorageController', + 'form controller class' => array( + 'default' => 'Drupal\taxonomy\VocabularyFormController', + ), 'base table' => 'taxonomy_vocabulary', 'entity keys' => array( 'id' => 'vid', @@ -293,8 +299,8 @@ function taxonomy_menu() { ); $items['admin/structure/taxonomy/add'] = array( 'title' => 'Add vocabulary', - 'page callback' => 'drupal_get_form', - 'page arguments' => array('taxonomy_form_vocabulary'), + 'page callback' => 'entity_get_form', + 'page arguments' => array('taxonomy_vocabulary'), 'access arguments' => array('administer taxonomy'), 'type' => MENU_LOCAL_ACTION, 'file' => 'taxonomy.admin.inc', @@ -315,10 +321,10 @@ function taxonomy_menu() { ); $items['taxonomy/term/%taxonomy_term/edit'] = array( 'title' => 'Edit', - 'page callback' => 'drupal_get_form', + 'page callback' => 'entity_get_form', // Pass a NULL argument to ensure that additional path components are not // passed to taxonomy_form_term() as the vocabulary machine name argument. - 'page arguments' => array('taxonomy_form_term', 2, NULL), + 'page arguments' => array(2), 'access callback' => 'taxonomy_term_access', 'access arguments' => array('edit', 2), 'type' => MENU_LOCAL_TASK, @@ -368,8 +374,8 @@ function taxonomy_menu() { ); $items['admin/structure/taxonomy/%taxonomy_vocabulary_machine_name/edit'] = array( 'title' => 'Edit', - 'page callback' => 'drupal_get_form', - 'page arguments' => array('taxonomy_form_vocabulary', 3), + 'page callback' => 'entity_get_form', + 'page arguments' => array(3), 'access arguments' => array('administer taxonomy'), 'type' => MENU_LOCAL_TASK, 'weight' => -10, @@ -378,8 +384,8 @@ function taxonomy_menu() { $items['admin/structure/taxonomy/%taxonomy_vocabulary_machine_name/add'] = array( 'title' => 'Add term', - 'page callback' => 'drupal_get_form', - 'page arguments' => array('taxonomy_form_term', NULL, 3), + 'page callback' => 'taxonomy_term_add', + 'page arguments' => array(3), 'access arguments' => array('administer taxonomy'), 'type' => MENU_LOCAL_ACTION, 'file' => 'taxonomy.admin.inc', diff --git a/core/modules/user/lib/Drupal/user/ProfileFormController.php b/core/modules/user/lib/Drupal/user/ProfileFormController.php new file mode 100644 index 0000000..542e411 --- /dev/null +++ b/core/modules/user/lib/Drupal/user/ProfileFormController.php @@ -0,0 +1,75 @@ + 'submit', + '#value' => t('Cancel account'), + '#submit' => array('user_edit_cancel_submit'), + '#access' => $account->uid > 1 && (($account->uid == $GLOBALS['user']->uid && user_access('cancel account')) || user_access('administer users')), + ); + + return $element; + } + + /** + * @see Drupal\entity\EntityFormController::submit() + */ + public function submit(array $form, array &$form_state) { + // @todo Consider moving this into the parent method. + // Remove unneeded values. + form_state_values_clean($form_state); + parent::submit($form, $form_state); + } + + /** + * @see Drupal\entity\EntityFormController::save() + */ + public function save(array $form, array &$form_state) { + $account = EntityFormController::getEntity($form_state); + $account->save(); + $form_state['values']['uid'] = $account->id(); + + // Clear the page cache because pages can contain usernames and/or profile + // information: + cache_invalidate(array('content' => TRUE)); + + drupal_set_message(t('The changes have been saved.')); + } +} diff --git a/core/modules/user/lib/Drupal/user/RegisterFormController.php b/core/modules/user/lib/Drupal/user/RegisterFormController.php new file mode 100644 index 0000000..1a590d2 --- /dev/null +++ b/core/modules/user/lib/Drupal/user/RegisterFormController.php @@ -0,0 +1,62 @@ +uid; } + + /** + * Implements Drupal\entity\Entity::form() + */ + public function form($context = 'profile') { + return parent::form($context); + } } diff --git a/core/modules/user/user.admin.inc b/core/modules/user/user.admin.inc index 58b0218..f044c64 100644 --- a/core/modules/user/user.admin.inc +++ b/core/modules/user/user.admin.inc @@ -11,7 +11,7 @@ function user_admin($callback_arg = '') { switch ($op) { case t('Create new account'): case 'create': - $build['user_register'] = drupal_get_form('user_register_form'); + $build['user_register'] = entity_get_form('user', 'register'); break; default: if (!empty($_POST['accounts']) && isset($_POST['operation']) && ($_POST['operation'] == 'cancel')) { diff --git a/core/modules/user/user.module b/core/modules/user/user.module index 2250110..70658e0 100644 --- a/core/modules/user/user.module +++ b/core/modules/user/user.module @@ -1,5 +1,7 @@ array( 'label' => t('User'), + 'entity class' => 'Drupal\user\User', 'controller class' => 'Drupal\user\UserStorageController', + 'form controller class' => array( + 'profile' => 'Drupal\user\ProfileFormController', + 'register' => 'Drupal\user\RegisterFormController', + ), 'base table' => 'users', 'uri callback' => 'user_uri', 'label callback' => 'user_label', 'fieldable' => TRUE, - 'entity class' => 'Drupal\user\User', 'entity keys' => array( 'id' => 'uid', ), @@ -1497,8 +1503,8 @@ function user_menu() { $items['user/register'] = array( 'title' => 'Create new account', - 'page callback' => 'drupal_get_form', - 'page arguments' => array('user_register_form'), + 'page callback' => 'entity_get_form', + 'page arguments' => array('user', 'register'), 'access callback' => 'user_register_access', 'type' => MENU_LOCAL_TASK, ); @@ -1657,8 +1663,8 @@ function user_menu() { $items['user/%user/edit'] = array( 'title' => 'Edit', - 'page callback' => 'drupal_get_form', - 'page arguments' => array('user_profile_form', 1), + 'page callback' => 'entity_get_form', + 'page arguments' => array(1, 'profile'), 'access callback' => 'user_edit_access', 'access arguments' => array(1), 'type' => MENU_LOCAL_TASK, @@ -3497,7 +3503,7 @@ function user_form_field_ui_field_edit_form_submit($form, &$form_state) { * @see user_account_form_validate() * @see user_register_submit() */ -function user_register_form($form, &$form_state) { +function _user_register_form($form, &$form_state) { global $user; $admin = user_access('administer users'); @@ -3538,54 +3544,21 @@ function user_register_form($form, &$form_state) { $form_state['redirect'] = current_path(); } - $form['actions'] = array('#type' => 'actions'); - $form['actions']['submit'] = array( - '#type' => 'submit', - '#value' => t('Create new account'), - ); - - $form['#validate'][] = 'user_register_validate'; - // Add the final user registration form submit handler. - $form['#submit'][] = 'user_register_submit'; - return $form; } /** - * Validation function for the user registration form. - */ -function user_register_validate($form, &$form_state) { - entity_form_field_validate('user', $form, $form_state); -} - -/** * Submit handler for the user registration form. * - * This function is shared by the installation form and the normal registration form, - * which is why it can't be in the user.pages.inc file. - * - * @see user_register_form() + * This function is shared by the installation form and the normal registration + * form, which is why it can't be in the user.pages.inc file. */ function user_register_submit($form, &$form_state) { + $account = EntityFormController::getEntity($form_state); + $pass = $account->pass; $admin = $form_state['values']['administer_users']; - - if (!variable_get('user_email_verification', TRUE) || $admin) { - $pass = $form_state['values']['pass']; - } - else { - $pass = user_password(); - } $notify = !empty($form_state['values']['notify']); - // Remove unneeded values. - form_state_values_clean($form_state); - - $form_state['values']['pass'] = $pass; - $form_state['values']['init'] = $form_state['values']['mail']; - - $account = $form['#user']; - - entity_form_submit_build_entity('user', $account, $form, $form_state); $status = $account->save(); // Terminate if an error occurred while saving the account. diff --git a/core/modules/user/user.pages.inc b/core/modules/user/user.pages.inc index 6cdfd25..ff36675 100644 --- a/core/modules/user/user.pages.inc +++ b/core/modules/user/user.pages.inc @@ -5,6 +5,8 @@ * User page callback file for the user module. */ +use Drupal\entity\EntityFormController; + use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; @@ -202,86 +204,6 @@ function template_preprocess_user_profile(&$variables) { } /** - * Form builder; edit a user account. - * - * @ingroup forms - * @see user_account_form() - * @see user_account_form_validate() - * @see user_profile_form_validate() - * @see user_profile_form_submit() - * @see user_cancel_confirm_form_submit() - */ -function user_profile_form($form, &$form_state, $account) { - global $user; - - // During initial form build, add the entity to the form state for use during - // form building and processing. During a rebuild, use what is in the form - // state. - if (!isset($form_state['user'])) { - $form_state['user'] = $account; - } - else { - $account = $form_state['user']; - } - - // @todo Legacy support. Modules are encouraged to access the entity using - // $form_state. Remove in Drupal 8. - $form['#user'] = $account; - - - user_account_form($form, $form_state); - // Attach field widgets. - field_attach_form('user', $account, $form, $form_state); - - $form['actions'] = array('#type' => 'actions'); - $form['actions']['submit'] = array( - '#type' => 'submit', - '#value' => t('Save'), - ); - $form['actions']['cancel'] = array( - '#type' => 'submit', - '#value' => t('Cancel account'), - '#submit' => array('user_edit_cancel_submit'), - '#access' => $account->uid > 1 && (($account->uid == $user->uid && user_access('cancel account')) || user_access('administer users')), - ); - - $form['#validate'][] = 'user_profile_form_validate'; - // Add the final user profile form submit handler. - $form['#submit'][] = 'user_profile_form_submit'; - - return $form; -} - -/** - * Validation function for the user account and profile editing form. - */ -function user_profile_form_validate($form, &$form_state) { - entity_form_field_validate('user', $form, $form_state); -} - -/** - * Submit function for the user account and profile editing form. - */ -function user_profile_form_submit($form, &$form_state) { - $account = $form_state['user']; - // Remove unneeded values. - form_state_values_clean($form_state); - - entity_form_submit_build_entity('user', $account, $form, $form_state); - $account->save(); - $form_state['values']['uid'] = $account->uid; - - if (!empty($edit['pass'])) { - // Remove the password reset tag since a new password was saved. - unset($_SESSION['pass_reset_'. $account->uid]); - } - // Clear the page cache because pages can contain usernames and/or profile information: - cache_invalidate(array('content' => TRUE)); - - drupal_set_message(t('The changes have been saved.')); -} - -/** * Submit function for the 'Cancel account' button on the user edit form. */ function user_edit_cancel_submit($form, &$form_state) {