Index: index.php =================================================================== RCS file: /cvs/drupal/drupal/index.php,v retrieving revision 1.94 diff -u -r1.94 index.php --- index.php 26 Dec 2007 08:46:48 -0000 1.94 +++ index.php 10 Mar 2008 21:20:44 -0000 @@ -32,8 +32,7 @@ } } elseif (isset($return)) { - // Print any value (including an empty string) except NULL or undefined: - print theme('page', $return); + print render('page', $return); } drupal_page_footer(); Index: includes/form.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/form.inc,v retrieving revision 1.267 diff -u -r1.267 form.inc --- includes/form.inc 12 Feb 2008 13:52:32 -0000 1.267 +++ includes/form.inc 10 Mar 2008 21:20:45 -0000 @@ -433,7 +433,9 @@ // however, we'll skip this and let the calling function examine // the resulting $form_state bundle itself. if (!$form['#programmed'] && empty($form_state['rebuild']) && empty($form_state['storage'])) { - drupal_redirect_form($form, $form_state['redirect']); + global $request_mode; + // TODO - more comments here, and update the above comments. + module_invoke_all('after_process_form', $request_mode, $form, $form_state); } } } @@ -606,8 +608,13 @@ * @param $redirect * An optional value containing the destination path to redirect * to if none is specified by the form. + * @param $do_goto + * Boolean flag. + * If true, call to drupal_goto to do the redirect. + * If false, return the url of the redirect, but don't go there. This allows + * for alternal flows of control, such as AJAX or webservices. */ -function drupal_redirect_form($form, $redirect = NULL) { +function drupal_redirect_form($form, $redirect = NULL, $do_goto = TRUE) { $goto = NULL; if (isset($redirect)) { $goto = $redirect; @@ -615,17 +622,31 @@ if ($goto !== FALSE && isset($form['#redirect'])) { $goto = $form['#redirect']; } - if (!isset($goto) || ($goto !== FALSE)) { - if (isset($goto)) { - if (is_array($goto)) { - call_user_func_array('drupal_goto', $goto); + if (!isset($goto) || ($goto !== FALSE)) { + // If $do_goto is set, call drupal_goto and do the redirect & exit. + if ($do_goto) { + if (isset($goto)) { + if (is_array($goto)) { + call_user_func_array('drupal_goto', $goto); + } + else { + drupal_goto($goto); + } } - else { - drupal_goto($goto); + drupal_goto($_GET['q']); + } + else { // If $do_goto is false, just calculate and return the next url. + if (isset($goto)) { + if (is_array($goto)) { + return call_user_func_array('drupal_get_goto_url', $goto); + } + else { + return drupal_get_goto_url($goto); + } } + return drupal_get_goto_url($_GET['q']); } - drupal_goto($_GET['q']); - } + } } /** Index: includes/bootstrap.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/bootstrap.inc,v retrieving revision 1.206 diff -u -r1.206 bootstrap.inc --- includes/bootstrap.inc 10 Jan 2008 22:47:17 -0000 1.206 +++ includes/bootstrap.inc 10 Mar 2008 21:20:44 -0000 @@ -271,7 +271,7 @@ * session name correctly. */ function conf_init() { - global $base_url, $base_path, $base_root; + global $base_url, $base_path, $base_root, $render_mode, $request_mode; // Export the following settings.php variables to the global namespace global $db_url, $db_prefix, $cookie_domain, $conf, $installed_profile, $update_free_access; @@ -337,6 +337,10 @@ ini_set('session.cookie_domain', $cookie_domain); } session_name('SESS'. md5($session_name)); + + // TODO - add comments to describe what these globals control. + $render_mode = isset($_SERVER['HTTP_X_DRUPAL_RENDER_MODE']) ? $_SERVER['HTTP_X_DRUPAL_RENDER_MODE'] : 'xhtml/plain'; + $request_mode = isset($_SERVER['HTTP_X_REQUESTED_WITH']) ? $_SERVER['HTTP_X_REQUESTED_WITH'] : 'standard'; } /** Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.759 diff -u -r1.759 common.inc --- includes/common.inc 20 Feb 2008 13:38:32 -0000 1.759 +++ includes/common.inc 10 Mar 2008 21:20:44 -0000 @@ -293,14 +293,14 @@ * supported. * @see drupal_get_destination() */ -function drupal_goto($path = '', $query = NULL, $fragment = NULL, $http_response_code = 302) { - +// TODO - update comments to reflect breaking drupal_goto into two functions. +function drupal_get_goto_url($path = '', $query = NULL, $fragment = NULL) { if (isset($_REQUEST['destination'])) { extract(parse_url(urldecode($_REQUEST['destination']))); } else if (isset($_REQUEST['edit']['destination'])) { extract(parse_url(urldecode($_REQUEST['edit']['destination']))); - } + } $url = url($path, array('query' => $query, 'fragment' => $fragment, 'absolute' => TRUE)); // Remove newlines from the URL to avoid header injection attacks. @@ -311,19 +311,25 @@ if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') { module_invoke_all('exit', $url); } + return $url; +} +function drupal_goto($path = '', $query = NULL, $fragment = NULL, $http_response_code = 302) { + $url = drupal_get_goto_url($path, $query, $fragment); + // Even though session_write_close() is registered as a shutdown function, we // need all session data written to the database before redirecting. session_write_close(); - + header('Location: '. $url, TRUE, $http_response_code); - + // The "Location" header sends a redirect status code to the HTTP daemon. In // some cases this can be wrong, so we make sure none of the code below the // drupal_goto() call gets executed upon redirection. exit(); } + /** * Generates a site off-line message. */ @@ -2659,6 +2665,44 @@ } } +/** + * Abstract the Drupal rendering system. + * Allow renderers other than the theme system to produce content output. + * Example of use: render('page', $content); + * + * @param + * Array starting with the element being rendered (ex: 'page'), + * followed by the content arguments. + * + * @return + * Rendered content. + */ +function render() { + global $render_mode; + $args = func_get_args(); + +// TODO - cache the renderer dispatch table. +// static $renderers; +// if (!$renderers) { +// // look in the cache +// //$renderers = cache_get('renderers'); +// if (!$renderers) { +// $renderers = module_invoke_all('renderers'); // Build the renderer dispatch table +// cache_set('renderers', $renderers); +// } +// } + + $renderers = module_invoke_all('renderers'); // Build the renderer dispatch table + $renderer = $renderers[$render_mode]; + if ($renderer) { + return call_user_func_array($renderer, $args); + } + else { + drupal_set_message(t('Attempting to use an unsupported render mode %render_mode.', array('%render_mode' => $render_mode)), 'error'); + $args = func_get_args(); + return call_user_func_array('theme', $args); + } +} /** * Renders HTML given a structured array tree. Index: modules/system/system.module =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.module,v retrieving revision 1.592 diff -u -r1.592 system.module --- modules/system/system.module 20 Feb 2008 13:46:41 -0000 1.592 +++ modules/system/system.module 10 Mar 2008 21:20:45 -0000 @@ -1887,6 +1887,77 @@ } /** + * Implements hook_renderers. + * Provide default system renderers. + */ +function system_renderers() { + return array( + 'xhtml/plain' => 'system_render_default', + 'json/popup' => 'system_render_json_popup' + ); +} + +/** + * Defualt Drupal renderer. + * Use the theme system to render to xhtml, for consumption by browsers. + * + * @return + * Themed content. + */ +function system_render_default() { + $args = func_get_args(); + return call_user_func_array('theme', $args); +} + +/** + * Render the page as JSON, for consumption by AJAX calls. + * Optimized for AJAX popups. + * + * @return + * JSON object with metadata and themed page content. + */ +function system_render_json_popup() { + $args = func_get_args(); + $hook = array_shift($args); + switch ($hook) { + case 'page': // render('page', $content); + return drupal_json(array( + 'status' => 'ok', + 'title' => drupal_get_title(), + 'messages' => theme('status_messages'), + 'path' => $_GET['q'], + 'content' => $args[0], + )); + default: + return drupal_json(array( + 'status' => 'error', + 'messages' => "No way to render '$hook' in mode 'json/popup'", + )); + } +} + +/** + * Implements hook_after_process_form + */ +function system_after_process_form($request_mode, $form, $form_state) { + switch ($request_mode) { + case 'XMLHttpRequest': + // Request comes from jQuery AJAX call. + // Return status, next page url and form state as JSON. + $url = drupal_redirect_form($form, $form_state['redirect'], FALSE); + print drupal_json(array( + 'status' => 'redirect', + 'path' => $url, + 'form_state' => $form_state, + )); + exit; + case 'standard': + // Default Drupal behavior, redirect to the next page. + drupal_redirect_form($form, $form_state['redirect']); + } +} + +/** * Format the Powered by Drupal text. * * @ingroup themeable