diff --git a/core/includes/form.inc b/core/includes/form.inc index 8059ea1..6583a7a 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -159,6 +159,8 @@ function drupal_get_form($form_id) { * - build_info: Internal. An associative array of information stored by Form * API that is necessary to build and rebuild the form from cache when the * original context may no longer be available: + * - callback: The actual callback to be used to retrieve the form array. If + * none is provided $form_id is used instead. Can be any callable type. * - args: A list of arguments to pass to the form constructor. * - files: An optional array defining include files that need to be loaded * for building the form. Each array entry may be the path to a file or @@ -747,9 +749,13 @@ function drupal_retrieve_form($form_id, &$form_state) { // the constructor function itself. $args = $form_state['build_info']['args']; - // We first check to see if there's a function named after the $form_id. + // If an explicit form builder callback is defined we just use it, otherwise + // we look for a function named after the $form_id. + $callback = !empty($form_state['build_info']['callback']) ? $form_state['build_info']['callback'] : $form_id; + + // We first check to see if there is a valid form builder callback defined. // If there is, we simply pass the arguments on to it to get the form. - if (!function_exists($form_id)) { + if (!is_callable($callback)) { // In cases where many form_ids need to share a central constructor function, // such as the node editing form, modules can implement hook_forms(). It // maps one or more form_ids to the correct constructor functions. @@ -808,7 +814,7 @@ function drupal_retrieve_form($form_id, &$form_state) { // If $callback was returned by a hook_forms() implementation, call it. // Otherwise, call the function named after the form id. - $form = call_user_func_array(isset($callback) ? $callback : $form_id, $args); + $form = call_user_func_array($callback, $args); $form['#form_id'] = $form_id; return $form; @@ -1469,7 +1475,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; } @@ -1812,7 +1818,7 @@ function form_builder($form_id, &$element, &$form_state) { // checkboxes and files. if (isset($element['#process']) && !$element['#processed']) { foreach ($element['#process'] as $process) { - $element = $process($element, $form_state, $form_state['complete_form']); + $element = call_user_func_array($process, array(&$element, &$form_state, &$form_state['complete_form'])); } $element['#processed'] = TRUE; } diff --git a/core/modules/book/book.module b/core/modules/book/book.module index 1087e23..48dda36 100644 --- a/core/modules/book/book.module +++ b/core/modules/book/book.module @@ -5,6 +5,8 @@ * Allows users to create and organize related content in an outline. */ +use Drupal\entity\EntityFormController; + use Drupal\node\Node; /** @@ -462,7 +464,8 @@ function book_form_node_form_alter(&$form, &$form_state, $form_id) { * @see book_form_node_form_alter() */ function book_pick_book_nojs_submit($form, &$form_state) { - $form_state['node']->book = $form_state['values']['book']; + $node = EntityFormController::getFormInstance($form_state)->getEntity(); + $node->book = $form_state['values']['book']; $form_state['rebuild'] = TRUE; } diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module index 9f47d37..3186349 100644 --- a/core/modules/comment/comment.module +++ b/core/modules/comment/comment.module @@ -9,6 +9,8 @@ * book page, etc. */ +use Drupal\entity\EntityFormController; + use Drupal\node\Node; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -105,8 +107,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', @@ -769,8 +774,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_add($node); } if ($additions) { @@ -786,6 +790,15 @@ function comment_node_page_additions(Node $node) { } /** + * Returns a rendered form to comment the given node. + */ +function comment_add($node, $pid = NULL) { + $values = array('nid' => $node->nid, 'pid' => $pid, 'node_type' => 'comment_node_' . $node->type); + $comment = entity_create('comment', $values); + return entity_get_form($comment); +} + +/** * Retrieves comments for a thread. * * @param Drupal\node\Node $node @@ -1654,19 +1667,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 entity_get_form($comment); } /** @@ -1681,18 +1682,10 @@ function comment_form($form, &$form_state, Comment $comment) { global $user; $language_content = drupal_container()->get(LANGUAGE_TYPE_CONTENT); - // During initial form build, add the comment 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['comment'])) { - $form_state['comment'] = $comment; - } - else { - $comment = $form_state['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'); @@ -1852,24 +1845,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; } @@ -1878,7 +1855,7 @@ function comment_form($form, &$form_state, Comment $comment) { * Form submission handler for the 'preview' button in comment_form(). */ function comment_form_build_preview($form, &$form_state) { - $comment = comment_form_submit_build_comment($form, $form_state); + $comment = EntityFormController::getFormInstance($form_state)->getEntity(); $form_state['comment_preview'] = comment_preview($comment); $form_state['rebuild'] = TRUE; } @@ -1952,8 +1929,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']); @@ -2033,26 +2008,6 @@ function comment_submit(Comment $comment) { } /** - * Updates the comment entity by processing the submission's values. - * - * This is the default builder function for the comment form. It is called - * during the "Save" and "Preview" submit handlers to retrieve the entity to - * save or preview. This function can also be called by a "Next" button of a - * wizard to update the form state's entity with the current step's values - * before proceeding to the next step. - * - * @see comment_form() - * @see comment_form_preview() - * @see comment_form_submit() - */ -function comment_form_submit_build_comment($form, &$form_state) { - $comment = $form_state['comment']; - entity_form_submit_build_entity('comment', $comment, $form, $form_state); - comment_submit($comment); - return $comment; -} - -/** * Form submission handler for comment_form(). * * @see comment_form_validate() @@ -2060,7 +2015,7 @@ function comment_form_submit_build_comment($form, &$form_state) { */ function comment_form_submit($form, &$form_state) { $node = node_load($form_state['values']['nid']); - $comment = comment_form_submit_build_comment($form, $form_state); + $comment = EntityFormController::getFormInstance($form_state)->getEntity(); if (user_access('post comments') && (user_access('administer comments') || $node->comment == COMMENT_NODE_OPEN)) { // Save the anonymous user information to a cookie for reuse. if (user_is_anonymous()) { diff --git a/core/modules/comment/comment.pages.inc b/core/modules/comment/comment.pages.inc index 5176846..bba1517 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_add($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_add($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..1f45286 --- /dev/null +++ b/core/modules/comment/lib/Drupal/comment/CommentFormController.php @@ -0,0 +1,76 @@ +getEntity()); + return parent::form($form, $form_state); + } + + /** + * @see Drupal\entity\EntityFormController::actions() + */ + protected function actions(array $form, array &$form_state) { + $element = parent::actions($form, $form_state); + $comment = $this->getEntity(); + $node = $form_state['node']; + $preview_mode = variable_get('comment_preview_' . $node->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::submit() + */ + public function submit(array $form, array &$form_state) { + parent::submit($form, $form_state); + comment_submit($this->getEntity()); + } + + /** + * @see Drupal\entity\EntityFormController::save() + */ + public function save(array $form, array &$form_state) { + comment_form_submit($form, $form_state); + } +} diff --git a/core/modules/entity/entity.api.php b/core/modules/entity/entity.api.php index 02d8754..1a65836 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 associative array where the keys are the names + * of the different form contexts and the values are the names of the form + * controller classes. To facilitate the case where an entity form varies + * only slightly between different contexts, the name of the context is + * passed to the constructor of the form controller class. In that way, one + * class can be used for multiple contexts. * - 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..5ae2d30 100644 --- a/core/modules/entity/entity.module +++ b/core/modules/entity/entity.module @@ -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,124 @@ function entity_form_field_validate($entity_type, $form, &$form_state) { } /** + * Returns an entity form controller for the given context. + * + * Since there might be different scenarios in which an entity is edited, + * multiple form controllers suitable to the different contexts may be defined. + * If no controller is found for the default context, the base class will be + * used. If a non-existing non-default context is specified an exception will be + * thrown. + * + * @see hook_entity_info() + * + * @param $entity_type + * The type of the entity. + * @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_form_controller($entity_type, $context = 'default') { + $info = entity_get_info($entity_type); + + // Check whether there is a form controller class for the specified context. + if (!empty($info['form controller class'][$context])) { + $class = $info['form controller class'][$context]; + } + // If no controller is specified default to the base implementation. + elseif (empty($info['form controller class']) && $context == 'default') { + $class = 'Drupal\entity\EntityFormController'; + } + // If a non-existing context has been specified stop. + else { + throw new EntityMalformedException("Missing form controller for '$entity_type' in context '$context'"); + } + + return new $class($context); +} + +/** + * Returns the form id for the given entity and context. + * + * @param EntityInterface $entity + * The entity to be created or edited. + * @param $context + * (optional) The context for the form to be processed. + * + * @return + * A string representing the entity form id. + */ +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 the default form state for the given entity and context. + * + * @param EntityInterface $entity + * The entity to be created or edited. + * @param $context + * (optional) The context for the form to be processed. + * + * @return + * A $form_state array already filled the entity form controller. + */ +function entity_form_state_defaults(EntityInterface $entity, $context = 'default') { + $form_state = array(); + $controller = entity_form_controller($entity->entityType(), $context); + $form_state['build_info']['callback'] = array($controller, 'build'); + $form_state['build_info']['base_form_id'] = $entity->entityType() . '_form'; + $form_state['build_info']['args'] = array($entity); + return $form_state; +} + +/** + * Retrieves, populates, and processes an entity form. + * + * @param EntityInterface $entity + * The entity to be created or edited. + * @param $context + * (optional) The context for the form to be submitted. + * @param $form_state + * (optional) A keyed array containing the current state of the form. + * + * @return + * A $form_state array already filled with the entity form controller. + */ +function entity_form_submit(EntityInterface $entity, $context = 'default', &$form_state = array()) { + $form_state += entity_form_state_defaults($entity, $context); + $form_id = entity_form_id($entity, $context); + drupal_form_submit($form_id, $form_state); +} + +/** + * Returns the rendered entity form for the given entity and context. + * + * @param EntityInterface $entity + * The entity to be created or edited. + * @param $context + * (optional) The context for the form to be returned. + * + * @return + * A rendered edit form for the given entity. + */ +function entity_get_form(EntityInterface $entity, $context = 'default') { + $form_state = entity_form_state_defaults($entity, $context); + $form_id = entity_form_id($entity, $context); + return drupal_build_form($form_id, $form_state); +} + +/** * 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..e2afd2a 100644 --- a/core/modules/entity/lib/Drupal/entity/Entity.php +++ b/core/modules/entity/lib/Drupal/entity/Entity.php @@ -269,5 +269,4 @@ class Entity implements EntityInterface { public function isCurrentRevision() { return $this->isCurrentRevision; } - } 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..f5c45f1 --- /dev/null +++ b/core/modules/entity/lib/Drupal/entity/EntityFormController.php @@ -0,0 +1,331 @@ +context = $context; + } + + /** + * Builds an entity 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) { + // Check whether we already have a controller in the form state to detect a + // form rebuild. + $controller = self::getFormInstance($form_state); + + // Set or update the entity form controller in the form state. + self::setFormInstance($form_state, $this); + + // During the 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 (empty($controller)) { + $this->setEntity($entity); + $this->prepareEntity(); + } + + // Retrieve the form array. + $form = $this->form($form, $form_state); + + // Add a process callback to refresh the wrapped form state after a cache + // retrieval. + if (!isset($form['#process'])) { + $form['#process'] = array(); + } + array_unshift($form['#process'], array($this, 'process')); + + // Retrieve the form actions array. + $actions = $this->actionsElement($form, $form_state); + if (!empty($actions)) { + $form['actions'] = $actions; + } + + return $form; + } + + /** + * Returns the actual form array to be built. + * + * @see EntityFormController::build() + */ + protected function form(array $form, array &$form_state) { + // @todo Exploit the Property API to generate the default widgets for the + // entity properties. + $entity = $this->getEntity(); + $info = $entity->entityInfo(); + if (!empty($info['fieldable'])) { + field_attach_form($entity->entityType(), $entity, $form, $form_state, $this->getFormLangcode()); + } + return $form; + } + + /** + * Returns the action form element for the current entity form. + */ + protected final function actionsElement(array $form, array &$form_state) { + $element = $this->actions($form, $form_state); + + // We cannot delete an entity that has not been created yet. + if (!$this->getEntity()->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_callback = 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_callback); + } + + 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) { + return array( + // @todo rename the action key from submit to save. + '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')), + ), + // @todo Consider introducing a 'preview' action here, since it is used by + // many entity types. + ); + } + + /** + * Form process callback. + * + * @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 process($form, &$form_state) { + // Update the form state reference to ensure that any modification to the + // entity object is correctly propagated. + self::setFormInstance($form_state, $this); + return $form; + } + + /** + * 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) { + $entity = $this->getEntity(); + + // @todo Exploit the Property API to validate the values submitted for the + // entity properties. + $info = $entity->entityInfo(); + if (!empty($info['fieldable'])) { + // @todo Consider inlining entity_form_field_validate() here. + 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. + $entity = $this->buildEntity($form, $form_state); + $this->setEntity($entity); + } + + /** + * Form submission 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) { + // @todo Perform common save operations. + } + + /** + * Form submission handler for the 'delete' 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 delete(array $form, array &$form_state) { + // @todo Perform common delete operations. + } + + /** + * Returns the code identifying the active form language. + */ + public function getFormLangcode() { + // @todo Introduce a new form language type (see hook_language_types_info()) + // to be used as the default active form language, should it be missing, so + // so that entity forms can be used to submit multilingual values. + $language = $this->getEntity()->language(); + return !empty($language->langcode) ? $language->langcode : NULL; + } + + /** + * Build a new entity object from the submitted 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 buildEntity(array $form, array &$form_state) { + $entity = clone $this->getEntity(); + // @todo Move entity_form_submit_build_entity() here. + entity_form_submit_build_entity($entity->entityType(), $entity, $form, $form_state); + return $entity; + } + + /** + * Returns the form entity. + * + * @param $form_state + * The current form state. + * + * @return Drupal\entity\EntityInterface + * The entity being the current form entity. + */ + public function getEntity() { + return $this->form_state['entity']; + } + + /** + * Sets the form entity. + * + * @param $form_state + * The current form state. + * @param Drupal\entity\Entity $entity + * The entity being edited in the current form. + */ + protected function setEntity(EntityInterface $entity) { + $this->form_state['entity'] = $entity; + } + + /** + * Prepares the form entity. + */ + protected function prepareEntity() { + // @todo Perform common prepare operations. + } + + /** + * 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. + */ + public static final function getFormInstance($form_state) { + return isset($form_state['controller']) ? $form_state['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, $controller) { + $controller->form_state = &$form_state; + $form_state['controller'] = $controller; + } +} diff --git a/core/modules/entity/lib/Drupal/entity/EntityInterface.php b/core/modules/entity/lib/Drupal/entity/EntityInterface.php index 699c424..7aedb48 100644 --- a/core/modules/entity/lib/Drupal/entity/EntityInterface.php +++ b/core/modules/entity/lib/Drupal/entity/EntityInterface.php @@ -204,5 +204,4 @@ interface EntityInterface { * TRUE if the entity is the current revision, FALSE otherwise. */ public function isCurrentRevision(); - } diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module index ca9bf7e..54ed906 100644 --- a/core/modules/forum/forum.module +++ b/core/modules/forum/forum.module @@ -592,7 +592,7 @@ function forum_field_storage_pre_update($entity_type, $entity, &$skip_fields) { /** * Implements hook_form_FORM_ID_alter() for taxonomy_form_vocabulary(). */ -function forum_form_taxonomy_form_vocabulary_alter(&$form, &$form_state, $form_id) { +function forum_form_taxonomy_vocabulary_form_alter(&$form, &$form_state, $form_id) { $vid = variable_get('forum_nav_vocabulary', 0); if (isset($form['vid']['#value']) && $form['vid']['#value'] == $vid) { $form['help_forum_vocab'] = array( @@ -609,9 +609,9 @@ function forum_form_taxonomy_form_vocabulary_alter(&$form, &$form_state, $form_i } /** - * Implements hook_form_FORM_ID_alter() for taxonomy_form_term(). + * Implements hook_form_FORM_ID_alter() for taxonomy_term_form(). */ -function forum_form_taxonomy_form_term_alter(&$form, &$form_state, $form_id) { +function forum_form_taxonomy_term_form_alter(&$form, &$form_state, $form_id) { $vid = variable_get('forum_nav_vocabulary', 0); if (isset($form['vid']['#value']) && $form['vid']['#value'] == $vid) { // Hide multiple parents select from forum terms. 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..c8a4872 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/NodeFormController.php @@ -0,0 +1,84 @@ +getEntity()); + } + + /** + * @see Drupal\entity\EntityFormController::form() + */ + protected function form(array $form, array &$form_state) { + $form = node_form($form, $form_state, $this->getEntity()); + return parent::form($form, $form_state); + } + + /** + * @see Drupal\entity\EntityFormController::actions() + */ + protected function actions(array $form, array &$form_state) { + $element = parent::actions($form, $form_state); + $node = $this->getEntity(); + $preview_mode = variable_get('node_preview_' . $node->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 3db5bbd..82bc445 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', @@ -3651,21 +3654,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 5d60f7c..7c97c0d 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 entity_get_form($node); } /** @@ -96,7 +98,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 = entity_get_form($node); return $output; } @@ -110,15 +112,14 @@ function node_add($type) { * @see node_form_submit_build_node() */ 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 + // The entity form controller 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::getFormInstance($form_state)->getEntity(); foreach ($form_state['values'] as $key => $value) { $node->{$key} = $value; } node_validate($node, $form, $form_state); - entity_form_field_validate('node', $form, $form_state); } /** @@ -134,17 +135,6 @@ function node_form_validate($form, &$form_state) { function node_form($form, &$form_state, Node $node) { global $user; - // During initial form build, add the node 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['node'])) { - node_object_prepare($node); - $form_state['node'] = $node; - } - else { - $node = $form_state['node']; - } - // Some special stuff when previewing a node. if (isset($form_state['node_preview'])) { $form['#prefix'] = $form_state['node_preview']; @@ -328,43 +318,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; } @@ -382,7 +342,7 @@ function node_form_delete_submit($form, &$form_state) { $destination = drupal_get_destination(); unset($_GET['destination']); } - $node = $form['#node']; + $node = EntityFormController::getFormInstance($form_state)->getEntity(); $form_state['redirect'] = array('node/' . $node->nid . '/delete', array('query' => $destination)); } @@ -494,9 +454,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(); @@ -567,19 +524,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::getFormInstance($form_state)->getEntity(); node_submit($node); foreach (module_implements('node_submit') as $module) { $function = $module . '_node_submit'; diff --git a/core/modules/openid/openid.module b/core/modules/openid/openid.module index 034c7c2..df076f8 100644 --- a/core/modules/openid/openid.module +++ b/core/modules/openid/openid.module @@ -732,7 +732,8 @@ function openid_authentication($response) { $form_state['values'] = array(); $form_state['values']['op'] = t('Create new account'); - drupal_form_submit('user_register_form', $form_state); + $account = entity_create('user', array()); + entity_form_submit($account, 'register', $form_state); if (empty($form_state['user'])) { module_invoke_all('openid_response', $response, NULL); diff --git a/core/modules/path/path.module b/core/modules/path/path.module index d01c623..7b73e20 100644 --- a/core/modules/path/path.module +++ b/core/modules/path/path.module @@ -232,9 +232,9 @@ function path_node_predelete(Node $node) { } /** - * Implements hook_form_FORM_ID_alter() for taxonomy_form_term(). + * Implements hook_form_FORM_ID_alter() for taxonomy_term_form(). */ -function path_form_taxonomy_form_term_alter(&$form, $form_state) { +function path_form_taxonomy_term_form_alter(&$form, $form_state) { // Make sure this does not show up on the delete confirmation form. if (empty($form_state['confirm_delete'])) { $path = (isset($form['#term']['tid']) ? path_load('taxonomy/term/' . $form['#term']['tid']) : array()); diff --git a/core/modules/poll/poll.module b/core/modules/poll/poll.module index 7954aaa..d43efd8 100644 --- a/core/modules/poll/poll.module +++ b/core/modules/poll/poll.module @@ -6,6 +6,8 @@ * choice questions. */ +use Drupal\entity\EntityFormController; + use Drupal\node\Node; /** @@ -336,6 +338,8 @@ function poll_form(Node $node, &$form_state) { drupal_get_path('module', 'poll') . '/poll.admin.css', ); + $form['#entity_builders'][] = 'poll_node_form_submit'; + return $form; } @@ -355,9 +359,10 @@ function poll_more_choices_submit($form, &$form_state) { } // Renumber the choices. This invalidates the corresponding key/value // associations in $form_state['input'], so clear that out. This requires - // poll_form() to rebuild the choices with the values in - // $form_state['node']->choice, which it does. - $form_state['node']->choice = array_values($form_state['values']['choice']); + // poll_form() to rebuild the choices with the values in $node->choice, which + // it does. + $node = EntityFormController::getFormInstance($form_state)->getEntity(); + $node->choice = array_values($form_state['values']['choice']); unset($form_state['input']['choice']); $form_state['rebuild'] = TRUE; } @@ -419,14 +424,14 @@ function poll_choice_js($form, $form_state) { } /** - * Form submit handler for node_form(). + * Entity builder for node_form(). * * Upon preview and final submission, we need to renumber poll choices and * create a teaser output. */ -function poll_node_form_submit(&$form, &$form_state) { +function poll_node_form_submit($entity_type, $entity, &$form, &$form_state) { // Renumber choices. - $form_state['values']['choice'] = array_values($form_state['values']['choice']); + $entity->choice = array_values($form_state['values']['choice']); $form_state['values']['teaser'] = poll_teaser((object) $form_state['values']); } diff --git a/core/modules/system/tests/modules/form_test/form_test.module b/core/modules/system/tests/modules/form_test/form_test.module index 86e810d..4dec033 100644 --- a/core/modules/system/tests/modules/form_test/form_test.module +++ b/core/modules/system/tests/modules/form_test/form_test.module @@ -2126,8 +2126,8 @@ function form_test_two_instances() { 'langcode' => LANGUAGE_NOT_SPECIFIED, )); $node2 = clone($node1); - $return['node_form_1'] = drupal_get_form('page_node_form', $node1); - $return['node_form_2'] = drupal_get_form('page_node_form', $node2); + $return['node_form_1'] = entity_get_form($node1); + $return['node_form_2'] = entity_get_form($node2); return $return; } diff --git a/core/modules/system/tests/modules/taxonomy_test/taxonomy_test.module b/core/modules/system/tests/modules/taxonomy_test/taxonomy_test.module index 0ec6da6..883ecc0 100644 --- a/core/modules/system/tests/modules/taxonomy_test/taxonomy_test.module +++ b/core/modules/system/tests/modules/taxonomy_test/taxonomy_test.module @@ -57,18 +57,16 @@ function taxonomy_test_taxonomy_term_delete(Term $term) { } /** - * Implements hook_form_alter(). + * Implements hook_form_FORM_ID_alter(). */ -function taxonomy_test_form_alter(&$form, $form_state, $form_id) { - if ($form_id == 'taxonomy_form_term') { - $antonym = taxonomy_test_get_antonym($form['#term']['tid']); - $form['advanced']['antonym'] = array( - '#type' => 'textfield', - '#title' => t('Antonym'), - '#default_value' => !empty($antonym) ? $antonym : '', - '#description' => t('Antonym of this term.') - ); - } +function taxonomy_test_form_taxonomy_term_form_alter(&$form, $form_state, $form_id) { + $antonym = taxonomy_test_get_antonym($form['#term']['tid']); + $form['advanced']['antonym'] = array( + '#type' => 'textfield', + '#title' => t('Antonym'), + '#default_value' => !empty($antonym) ? $antonym : '', + '#description' => t('Antonym of this term.') + ); } /** diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/TermFormController.php b/core/modules/taxonomy/lib/Drupal/taxonomy/TermFormController.php new file mode 100644 index 0000000..e700277 --- /dev/null +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/TermFormController.php @@ -0,0 +1,55 @@ +getEntity()); + return parent::form($form, $form_state); + } + + /** + * @see Drupal\entity\EntityFormController::validate() + */ + public function validate(array $form, array &$form_state) { + parent::validate($form, $form_state); + taxonomy_term_form_validate($form, $form_state); + } + + /** + * @see Drupal\entity\EntityFormController::submit() + */ + public function submit(array $form, array &$form_state) { + parent::submit($form, $form_state); + taxonomy_term_form_submit_build_taxonomy_term($form, $form_state); + } + + /** + * @see Drupal\entity\EntityFormController::save() + */ + public function save(array $form, array &$form_state) { + taxonomy_term_form_submit($form, $form_state); + } + + /** + * @see Drupal\entity\EntityFormController::delete() + */ + public function delete(array $form, array &$form_state) { + taxonomy_term_form_delete_submit($form, $form_state); + } +} diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermTest.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermTest.php index d041d16..a14cff8 100644 --- a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermTest.php +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermTest.php @@ -278,7 +278,7 @@ class TermTest extends TaxonomyTestBase { 'description[value]' => $this->randomName(100), ); // Explicitly set the parents field to 'root', to ensure that - // taxonomy_form_term_submit() handles the invalid term ID correctly. + // taxonomy_term_form_submit() handles the invalid term ID correctly. $edit['parent[]'] = array(0); // Create the term to edit. @@ -329,7 +329,7 @@ class TermTest extends TaxonomyTestBase { $this->drupalGet('taxonomy/term/' . $term->tid . '/feed'); // Check that the term edit page does not try to interpret additional path - // components as arguments for taxonomy_form_term(). + // components as arguments for taxonomy_term_form(). $this->drupalGet('taxonomy/term/' . $term->tid . '/edit/' . $this->randomName()); // Delete the term. diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyFormController.php b/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyFormController.php new file mode 100644 index 0000000..8b1c2c9 --- /dev/null +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyFormController.php @@ -0,0 +1,64 @@ +getEntity()); + return parent::form($form, $form_state); + } + + /** + * Returns an array of supported actions for the current entity form. + */ + protected function actions(array $form, array &$form_state) { + // If we are displaying the delete confirmation skip the regular actions. + return empty($form_state['confirm_delete']) ? parent::actions($form, $form_state) : array(); + } + + /** + * @see Drupal\entity\EntityFormController::validate() + */ + public function validate(array $form, array &$form_state) { + parent::validate($form, $form_state); + taxonomy_form_vocabulary_validate($form, $form_state); + } + + /** + * @see Drupal\entity\EntityFormController::submit() + */ + public function submit(array $form, array &$form_state) { + // @todo We should not be calling taxonomy_vocabulary_confirm_delete() from + // within taxonomy_form_vocabulary(). + if ($form_state['triggering_element']['#value'] == t('Delete')) { + // Rebuild the form to confirm vocabulary deletion. + $form_state['rebuild'] = TRUE; + $form_state['confirm_delete'] = TRUE; + } + else { + parent::submit($form, $form_state); + } + } + + /** + * @see Drupal\entity\EntityFormController::save() + */ + public function save(array $form, array &$form_state) { + taxonomy_form_vocabulary_submit($form, $form_state); + } +} diff --git a/core/modules/taxonomy/taxonomy.admin.inc b/core/modules/taxonomy/taxonomy.admin.inc index be020a6..dc6a5af 100644 --- a/core/modules/taxonomy/taxonomy.admin.inc +++ b/core/modules/taxonomy/taxonomy.admin.inc @@ -7,6 +7,7 @@ use Drupal\taxonomy\Term; use Drupal\taxonomy\Vocabulary; +use Drupal\entity\EntityFormController; /** * Form builder to list and manage vocabularies. @@ -102,6 +103,14 @@ function theme_taxonomy_overview_vocabularies($variables) { } /** + * Page callback: provides the vocabulary creation form. + */ +function taxonomy_vocabulary_add() { + $vocabulary = entity_create('taxonomy_vocabulary', array()); + return entity_get_form($vocabulary); +} + +/** * Form builder for the vocabulary editing form. * * @param Drupal\taxonomy\Vocabulary|null $vocabulary @@ -167,13 +176,9 @@ function taxonomy_form_vocabulary($form, &$form_state, Vocabulary $vocabulary = '#value' => '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 +212,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::getFormInstance($form_state)->getEntity(); // Prevent leading and trailing spaces in vocabulary names. $vocabulary->name = trim($vocabulary->name); @@ -643,6 +640,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 entity_get_form($term); +} + +/** * Form function for the term edit form. * * @param Drupal\taxonomy\Term|null $term @@ -653,10 +658,10 @@ function theme_taxonomy_overview_terms($variables) { * the term is omitted. * * @ingroup forms - * @see taxonomy_form_term_validate() - * @see taxonomy_form_term_submit() + * @see taxonomy_term_form_validate() + * @see taxonomy_term_form_submit() */ -function taxonomy_form_term($form, &$form_state, Term $term = NULL, Vocabulary $vocabulary = NULL) { +function taxonomy_term_form($form, &$form_state, Term $term = NULL, Vocabulary $vocabulary = NULL) { // During initial form build, add the term entity to the form state for use // during form building and processing. During a rebuild, use what is in the // form state. @@ -710,8 +715,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 +773,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(); } @@ -796,11 +783,9 @@ function taxonomy_form_term($form, &$form_state, Term $term = NULL, Vocabulary $ /** * Validation handler for the term form. * - * @see taxonomy_form_term() + * @see taxonomy_term_form() */ -function taxonomy_form_term_validate($form, &$form_state) { - entity_form_field_validate('taxonomy_term', $form, $form_state); - +function taxonomy_term_form_validate($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.')); @@ -810,10 +795,10 @@ function taxonomy_form_term_validate($form, &$form_state) { /** * Submit handler to insert or update a term. * - * @see taxonomy_form_term() + * @see taxonomy_term_form() */ -function taxonomy_form_term_submit($form, &$form_state) { - $term = taxonomy_form_term_submit_build_taxonomy_term($form, $form_state); +function taxonomy_term_form_submit($form, &$form_state) { + $term = EntityFormController::getFormInstance($form_state)->getEntity(); $status = taxonomy_term_save($term); switch ($status) { @@ -856,9 +841,8 @@ 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); +function taxonomy_term_form_submit_build_taxonomy_term($form, &$form_state) { + $term = EntityFormController::getFormInstance($form_state)->getEntity(); // Prevent leading and trailing spaces in term names. $term->name = trim($term->name); @@ -871,19 +855,19 @@ function taxonomy_form_term_submit_build_taxonomy_term($form, &$form_state) { } /** - * Form submission handler for the 'Delete' button for taxonomy_form_term(). + * Form submission handler for the 'Delete' button for taxonomy_term_form(). * - * @see taxonomy_form_term_validate() - * @see taxonomy_form_term_submit() + * @see taxonomy_term_form_validate() + * @see taxonomy_term_form_submit() */ -function taxonomy_form_term_delete_submit($form, &$form_state) { +function taxonomy_term_form_delete_submit($form, &$form_state) { $destination = array(); if (isset($_GET['destination'])) { $destination = drupal_get_destination(); unset($_GET['destination']); } - $term = $form['#term']; - $form_state['redirect'] = array('taxonomy/term/' . $term['tid'] . '/delete', array('query' => $destination)); + $term = EntityFormController::getFormInstance($form_state)->getEntity(); + $form_state['redirect'] = array('taxonomy/term/' . $term->tid . '/delete', array('query' => $destination)); } /** diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module index f55ac1d..7154bd3 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,7 @@ 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' => 'taxonomy_vocabulary_add', 'access arguments' => array('administer taxonomy'), 'type' => MENU_LOCAL_ACTION, 'file' => 'taxonomy.admin.inc', @@ -315,10 +320,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), + // passed to taxonomy_term_form() as the vocabulary machine name argument. + 'page arguments' => array(2), 'access callback' => 'taxonomy_term_access', 'access arguments' => array('edit', 2), 'type' => MENU_LOCAL_TASK, @@ -368,8 +373,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 +383,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..7f59e94 --- /dev/null +++ b/core/modules/user/lib/Drupal/user/ProfileFormController.php @@ -0,0 +1,76 @@ +getEntity(); + + user_account_form($form, $form_state); + + return parent::form($form, $form_state); + } + + /** + * @see Drupal\entity\EntityFormController::actions() + */ + protected function actions(array $form, array &$form_state) { + $element = parent::actions($form, $form_state); + $account = $this->getEntity(); + + // @todo Actually the cancel action can be assimilated to the delete one: we + // should alter it instead of providing a new one. + unset($element['delete']); + + $element['cancel'] = array( + '#type' => '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 = $this->getEntity(); + $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..5205a04 --- /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..fac9723 100644 --- a/core/modules/user/user.admin.inc +++ b/core/modules/user/user.admin.inc @@ -11,7 +11,8 @@ function user_admin($callback_arg = '') { switch ($op) { case t('Create new account'): case 'create': - $build['user_register'] = drupal_get_form('user_register_form'); + $account = entity_create('user', array()); + $build['user_register'] = entity_get_form($account, '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..8f9a819 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', ), @@ -711,8 +717,8 @@ function user_account_form(&$form, &$form_state) { global $user; $language_interface = drupal_container()->get(LANGUAGE_TYPE_INTERFACE); - $account = $form['#user']; - $register = ($form['#user']->uid > 0 ? FALSE : TRUE); + $account = EntityFormController::getFormInstance($form_state)->getEntity(); + $register = ($account->uid > 0 ? FALSE : TRUE); $admin = user_access('administer users'); @@ -1418,6 +1424,11 @@ function user_is_logged_in() { return (bool) $GLOBALS['user']->uid; } +function user_register() { + $account = entity_create('user', array()); + return entity_get_form($account, 'register'); +} + function user_register_access() { return user_is_anonymous() && variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL); } @@ -1497,8 +1508,7 @@ function user_menu() { $items['user/register'] = array( 'title' => 'Create new account', - 'page callback' => 'drupal_get_form', - 'page arguments' => array('user_register_form'), + 'page callback' => 'user_register', 'access callback' => 'user_register_access', 'type' => MENU_LOCAL_TASK, ); @@ -1657,8 +1667,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 +3507,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'); @@ -3515,7 +3525,7 @@ function user_register_form($form, &$form_state) { drupal_goto('user/' . $user->uid); } - $form['#user'] = entity_create('user', array()); + $form['#user'] = EntityFormController::getFormInstance($form_state)->getEntity(); $form['#attached']['library'][] = array('system', 'jquery.cookie'); $form['#attributes']['class'][] = 'user-info-from-cookie'; @@ -3538,54 +3548,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::getFormInstance($form_state)->getEntity(); + $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 b568f66..afa61b6 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\HttpFoundation\Request; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; @@ -204,86 +206,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) {