Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.1052 diff -u -p -r1.1052 common.inc --- includes/common.inc 26 Nov 2009 05:54:48 -0000 1.1052 +++ includes/common.inc 29 Nov 2009 20:05:58 -0000 @@ -4476,6 +4476,7 @@ function _drupal_bootstrap_full() { require_once DRUPAL_ROOT . '/includes/unicode.inc'; require_once DRUPAL_ROOT . '/includes/image.inc'; require_once DRUPAL_ROOT . '/includes/form.inc'; + require_once DRUPAL_ROOT . '/includes/entity.inc'; require_once DRUPAL_ROOT . '/includes/mail.inc'; require_once DRUPAL_ROOT . '/includes/actions.inc'; require_once DRUPAL_ROOT . '/includes/ajax.inc'; Index: includes/entity.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/entity.inc,v retrieving revision 1.3 diff -u -p -r1.3 entity.inc --- includes/entity.inc 31 Oct 2009 16:06:35 -0000 1.3 +++ includes/entity.inc 29 Nov 2009 20:24:36 -0000 @@ -298,3 +298,183 @@ class DrupalDefaultEntityController impl $this->entityCache += $entities; } } + +/** + * Wrapper for drupal_build_form() to use for fieldable entity forms. + * + * This is similar to drupal_get_form(), but additionally + * - stores the entity type in $form_state['storage']['entity']; e.g. 'node' or + * 'user'. + * - stores the initial entity object in $form_state['storage']['object'] upon + * first build of the form (but not on subsequent builds). + * - defines entity_form_wrapper() as 'wrapper_callback' to be invoked before + * the form constructor is invoked, which is the heart of the special + * fieldable entity form workflow. + * + * @see entity_form_wrapper() + * + * @param $obj_type + * The type of $object; e.g. 'node' or 'user'. + * @param $form_id + * The unique string identifying the desired form. See drupal_get_form() and + * drupal_build_form() for details. + * @param $object + * The object for which to build a form. + * @param ... + * Any additional arguments to pass to form constructor functions. + * + * @see drupal_build_form() + */ +function entity_get_form($obj_type, $form_id, $object = NULL) { + // Handle additional arguments to pass to form constructor, i.e. + // remove $obj_type and $form_id, but pass on everything else. + $args = func_get_args(); + array_shift($args); + array_shift($args); + $form_state['build_info']['args'] = $args; + + // Prepare initial form storage. + if (empty($form_state['storage'])) { + // Store the name of the entity we are processing. + $form_state['storage']['entity'] = $obj_type; + + // Prepare the initial object. + // The object in form storage is assigned by reference, so form constructor + // functions can work with either variable. + // @todo Leverage entity_create_stub_entity() here, see node_add(). + if (!isset($object)) { + // @todo After creating a stub entity, we need to update $args, which is + // currently not possible, because drupal_retrieve_form() uses private + // $args instead of $form_state['build_info']['args']. + $object = new stdClass; + } + $form_state['storage']['object'] = &$object; + } + + // Invoke the common wrapper callback common for all fieldable entities. + $form_state['wrapper_callback'] = 'entity_form_wrapper'; + + return drupal_build_form($form_id, $form_state); +} + +/** + * Form wrapper callback for fieldable entity forms. + * + * This function is invoked before the form constructor function (usually + * $form_id) to set up $form and $form_state for an fieldable entity form + * workflow: + * - form caching is enabled to cache all data in $form_state['storage']. It is + * important that form caching must not be disabled by any function in the + * entire form workflow. Otherwise, previously submitted form values will get + * lost. + * - field widgets are attached to the form. + * - entity_form_validate() is added as form validation handler to validate + * the submitted form values for fields and invoke an entity-specific + * ENTITY_validate() function, if existent. + * - entity_form_submit() is added as form submit handler to update the object + * in the form storage with the submitted form values, invoke an + * entity-specific ENTITY_submit() function (if existent), and process field + * values for attached fields. + * + * @see entity_get_form() + * @see entity_form_validate() + * @see entity_form_submit() + */ +function entity_form_wrapper($form, &$form_state) { + // Extract information that was initially setup by entity_get_form() or was + // reloaded from cache. + $obj_type = $form_state['storage']['entity']; + $object = $form_state['storage']['object']; + + // Enable form caching to make form storage persist across requests. This is + // required; otherwise, entity_get_form() would re-populate variables in the + // form storage freshly based on the passed in arguments. + $form_state['cache'] = TRUE; + + // Attach fields. + field_attach_form($obj_type, $object, $form, $form_state, (isset($object->language) ? $object->language : NULL)); + + // Add form validation handler for attached fields. + $form['#validate'][] = 'entity_form_validate'; + // Add default form-level validation handler, i.e. CALLBACK_validate(). + // Form buttons can override this by assigning custom validation handlers in + // their own #validate property. + // @todo drupal_prepare_form() actually adds FORMID_validate(), not callback. + if (function_exists($form_state['build_info']['callback'] . '_validate')) { + $form['#validate'][] = $form_state['build_info']['callback'] . '_validate'; + } + + // Add form submit handler for attached fields. + $form['#submit'][] = 'entity_form_submit'; + // Add default form-level submit handler, i.e. CALLBACK_submit(). + // Form buttons can override this by assigning custom submit handlers in + // their own #submit property. + // @todo drupal_prepare_form() actually adds FORMID_submit(), not callback. + if (function_exists($form_state['build_info']['callback'] . '_submit')) { + $form['#submit'][] = $form_state['build_info']['callback'] . '_submit'; + } + + return $form; +} + +/** + * Form validation handler for fieldable entity forms. + * + * @see entity_form_wrapper() + * @see entity_form_submit() + */ +function entity_form_validate($form, &$form_state) { + // Extract information. + $obj_type = $form_state['storage']['entity']; + $object = (object) $form_state['values']; + + // Invoke entity validation function, if existent; i.e. ENTITY_validate(). + $function = $obj_type . '_validate'; + if (function_exists($function)) { + $function($object, $form, $form_state); + } + // Invoke hook_ENTITY_validate() implementations. + $hook = $obj_type . '_validate'; + foreach (module_implements($hook) as $module) { + $function = $module . '_' . $hook; + $function($object, $form, $form_state); + } + + // Validate attached fields. + field_attach_form_validate($obj_type, $object, $form, $form_state); +} + +/** + * Form submit handler for fieldable entity forms. + * + * @see entity_form_wrapper() + * @see entity_form_validate() + */ +function entity_form_submit($form, &$form_state) { + // Update object in form storage with submitted values. + $form_state['storage']['object'] = (object) $form_state['values']; + + // Extract information. + $obj_type = $form_state['storage']['entity']; + $object = &$form_state['storage']['object']; + + // Invoke entity submit function, if existent; i.e. ENTITY_submit(). + $function = $obj_type . '_submit'; + if (function_exists($function)) { + $function($object, $form, $form_state); + } + // Invoke hook_ENTITY_submit() implementations. + $hook = $obj_type . '_submit'; + foreach (module_implements($hook) as $module) { + $function = $module . '_' . $hook; + $function($object, $form, $form_state); + } + + // Prepare submitted form values for attached fields for saving. + field_attach_submit($obj_type, $object, $form, $form_state); + + // @todo Form submit handlers should set this on their own. Not even sure why + // one can save a node with this being here (yet another FAPI bug?). + $form_state['rebuild'] = TRUE; +} + Index: includes/form.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/form.inc,v retrieving revision 1.408 diff -u -p -r1.408 form.inc --- includes/form.inc 28 Nov 2009 14:39:31 -0000 1.408 +++ includes/form.inc 29 Nov 2009 20:05:09 -0000 @@ -485,6 +485,10 @@ function drupal_form_submit($form_id, &$ function drupal_retrieve_form($form_id, &$form_state) { $forms = &drupal_static(__FUNCTION__); + // Initialize build information. + $form_state['build_info']['form_id'] = $form_id; + $form_state['build_info']['callback'] = $form_id; + // We save two copies of the incoming arguments: one for modules to use // when mapping form ids to constructor functions, and another to pass to // the constructor function itself. @@ -512,10 +516,11 @@ function drupal_retrieve_form($form_id, $args = array_merge($form_definition['callback arguments'], $args); } if (isset($form_definition['callback'])) { - $callback = $form_definition['callback']; + $form_state['build_info']['callback'] = $form_definition['callback']; } // In case $form_state['wrapper_callback'] is not defined already, we also // allow hook_forms() to define one. + // @todo Technically, this belongs into build_info as well. if (!isset($form_state['wrapper_callback']) && isset($form_definition['wrapper_callback'])) { $form_state['wrapper_callback'] = $form_definition['wrapper_callback']; } @@ -542,7 +547,8 @@ function drupal_retrieve_form($form_id, // 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($form_state['build_info']['callback'], $args); + // @todo Remove; already in build_info now. $form['#form_id'] = $form_id; return $form; Index: modules/node/node.module =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.module,v retrieving revision 1.1170 diff -u -p -r1.1170 node.module --- modules/node/node.module 29 Nov 2009 10:43:53 -0000 1.1170 +++ modules/node/node.module 29 Nov 2009 20:04:50 -0000 @@ -926,8 +926,6 @@ function node_submit($node) { } $node->created = !empty($node->date) ? strtotime($node->date) : REQUEST_TIME; $node->validated = TRUE; - - return $node; } /** Index: modules/node/node.pages.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.pages.inc,v retrieving revision 1.100 diff -u -p -r1.100 node.pages.inc --- modules/node/node.pages.inc 8 Nov 2009 10:02:41 -0000 1.100 +++ modules/node/node.pages.inc 29 Nov 2009 20:04:50 -0000 @@ -13,7 +13,7 @@ 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[FIELD_LANGUAGE_NONE][0]['value'])), PASS_THROUGH); - return drupal_get_form($node->type . '_node_form', $node); + return entity_get_form('node', $node->type . '_node_form', $node); } function node_add_page() { @@ -65,21 +65,15 @@ function node_add($type) { $node = (object)array('uid' => $user->uid, 'name' => (isset($user->name) ? $user->name : ''), 'type' => $type, 'language' => ''); 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', $node->type . '_node_form', $node); } return $output; } -function node_form_validate($form, &$form_state) { - $node = (object)$form_state['values']; - node_validate($node, $form); - - // Field validation. Requires access to $form_state, so this cannot be - // done in node_validate() as it currently exists. - field_attach_form_validate('node', $node, $form, $form_state); -} - +/** + * @todo Why is this a separate function? Only invoked by node_form(). + */ function node_object_prepare($node) { // Set up default values, if required. $node_options = variable_get('node_options_' . $node->type, array('status', 'promote')); @@ -110,9 +104,7 @@ function node_object_prepare($node) { function node_form($form, &$form_state, $node) { global $user; - if (isset($form_state['node'])) { - $node = (object)($form_state['node'] + (array)$node); - } + // @todo This is 100% #process. if (isset($form_state['node_preview'])) { $form['#prefix'] = $form_state['node_preview']; } @@ -151,6 +143,7 @@ function node_form($form, &$form_state, $form = array_merge_recursive($form, $extra); } + // @todo Remove. $form['#node'] = $node; $form['additional_settings'] = array( @@ -272,14 +265,13 @@ function node_form($form, &$form_state, '#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['buttons']['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'), + '#submit' => array('entity_form_submit', 'node_form_build_preview'), ); if (!empty($node->nid) && node_access('delete', $node)) { $form['buttons']['delete'] = array( @@ -289,11 +281,9 @@ function node_form($form, &$form_state, '#submit' => array('node_form_delete_submit'), ); } - $form['#validate'][] = 'node_form_validate'; $form['#theme'] = array($node->type . '_node_form', 'node_form'); $form['#builder_function'] = 'node_form_submit_build_node'; - field_attach_form('node', $node, $form, $form_state, $node->language); return $form; } @@ -311,10 +301,8 @@ function node_form_delete_submit($form, $form_state['redirect'] = array('node/' . $node->nid . '/delete', array('query' => $destination)); } - function node_form_build_preview($form, &$form_state) { - $node = node_form_submit_build_node($form, $form_state); - $form_state['node_preview'] = node_preview($node); + $form_state['node_preview'] = node_preview($form_state['storage']['object']); } /** @@ -413,7 +401,7 @@ function theme_node_preview($variables) } function node_form_submit($form, &$form_state) { - $node = node_form_submit_build_node($form, $form_state); + $node = $form_state['storage']['object']; $insert = empty($node->nid); node_save($node); $node_link = l(t('view'), 'node/' . $node->nid);