Index: index.php =================================================================== RCS file: /cvs/drupal/drupal/index.php,v retrieving revision 1.94 diff -u -p -r1.94 index.php --- index.php 26 Dec 2007 08:46:48 -0000 1.94 +++ index.php 8 May 2008 12:57:46 -0000 @@ -14,26 +14,5 @@ require_once './includes/bootstrap.inc'; drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); - -$return = menu_execute_active_handler(); - -// Menu status constants are integers; page content is a string. -if (is_int($return)) { - switch ($return) { - case MENU_NOT_FOUND: - drupal_not_found(); - break; - case MENU_ACCESS_DENIED: - drupal_access_denied(); - break; - case MENU_SITE_OFFLINE: - drupal_site_offline(); - break; - } -} -elseif (isset($return)) { - // Print any value (including an empty string) except NULL or undefined: - print theme('page', $return); -} - +drupal_page_content(); drupal_page_footer(); Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.765 diff -u -p -r1.765 common.inc --- includes/common.inc 6 May 2008 12:18:45 -0000 1.765 +++ includes/common.inc 8 May 2008 12:57:52 -0000 @@ -271,8 +271,8 @@ function drupal_get_destination() { * Drupal will ensure that messages set by drupal_set_message() and other * session data are written to the database before the user is redirected. * - * This function ends the request; use it rather than a print theme('page') - * statement in your menu callback. + * This function ends the request; use it rather than a + * return array('page', ...) statement in your menu callback. * * @param $path * A Drupal path or a full URL. @@ -362,7 +362,7 @@ function drupal_not_found() { } // To conserve CPU and bandwidth, omit the blocks. - print theme('page', $return, FALSE); + return array('page', $return, FALSE); } /** @@ -389,7 +389,7 @@ function drupal_access_denied() { drupal_set_title(t('Access denied')); $return = t('You are not authorized to access this page.'); } - print theme('page', $return); + return array('page', $return); } /** @@ -1454,6 +1454,85 @@ function l($text, $path, $options = arra } /** + * Return an array representing the rendered page. + * + * @param $content + * Follows the rules of the return value of the active menu handler. + * + * @return + * An array of page elements. + * - 'success' is TRUE or FALSE depending on the success of the request + * - 'data' is the error constant value on error, a variable array otherwise + */ +function drupal_page($content) { + $return = array('success' => TRUE); + switch ($content) { + case MENU_NOT_FOUND: + case MENU_ACCESS_DENIED: + case MENU_SITE_OFFLINE: + $return['success'] = FALSE; + $return['data'] = $content; + break; + default: + init_theme(); + $hooks = theme_get_registry(); + if (!isset($hooks['page'])) { + $return['success'] = FALSE; + break; + } + $info = $hooks['page']; + $variables = array('content' => $content); + if (isset($info['preprocess functions']) && is_array($info['preprocess functions'])) { + // We keep a reference through call_user_func_array(). + $args = array(&$variables, $hook); + foreach ($info['preprocess functions'] as $preprocess_function) { + if (function_exists($preprocess_function)) { + call_user_func_array($preprocess_function, $args); + } + } + } + $return['data'] = $variables; + break; + } + return $return; +} + +/** + * Call the active menu handler and output the result. + */ +function drupal_page_content() { + $return = menu_execute_active_handler(); + + if (isset($return)) { + // Menu status constants are integers; page content is a string, if defined. + if (is_int($return)) { + switch ($return) { + case MENU_NOT_FOUND: + list($hook, $content, $blocks) = drupal_not_found(); + break; + case MENU_ACCESS_DENIED: + list($hook, $content, $blocks) = drupal_access_denied(); + break; + case MENU_SITE_OFFLINE: + list($hook, $content, $blocks) = drupal_site_offline(); + break; + } + } + else { + list($hook, $content, $blocks) = array('page', $return, TRUE); + $return = TRUE; + } + } + + if (isset($hook)) { + // $hook should be a 'page' type hook, like 'maintenance_page'. + print theme($hook, $content, $blocks, TRUE, $return); + } + + // No output presented otherwise. +} + +/** * Perform end-of-request tasks. * * This function sets the page cache if appropriate, and allows modules to Index: includes/theme.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/theme.inc,v retrieving revision 1.422 diff -u -p -r1.422 theme.inc --- includes/theme.inc 6 May 2008 12:18:45 -0000 1.422 +++ includes/theme.inc 8 May 2008 12:57:52 -0000 @@ -589,6 +589,9 @@ function theme() { include_once($include_file); } if (isset($info['function'])) { + // Enable the redirection of output into formats other than XHTML, + // e.g. XML, JSON. + drupal_set_renderer($info['function'], $hook, $args); // The theme call is a function. $output = call_user_func_array($info['function'], $args); } @@ -659,6 +662,9 @@ function theme() { $template_file = $info['path'] . '/' . $template_file; } } + // Enable the redirection of output into formats other than XHTML, + // e.g. XML, JSON. + drupal_set_renderer($render_function, $hook, $variables); $output = $render_function($template_file, $variables); } // restore path_to_theme() @@ -1922,3 +1928,36 @@ function template_preprocess_block(&$var $variables['template_files'][] = 'block-' . $variables['block']->module . '-' . $variables['block']->delta; } +/** + * Enable modules to override the standard theme renderer for a theme call. + * + * Modules invoking hook_renderer() are called in order of weight, + * starting with the highest, and only the first response is accepted. + */ +function drupal_set_renderer(&$render_function, $hook, &$variables) { + foreach (array_reverse(module_implements('renderer')) as $module) { + // Construction needed to pass by reference. + $args = array($hook, &$variables); + if ($function = call_user_func_array($module . '_renderer', $args)) { + $render_function = $function; + return; + } + } +} + +/** + * Render function to output themed data in JSON format. + * + * The theme system passes two arguments, a template file path and the + * content to be rendered. Here we need only the second. + */ +function drupal_render_js() { + $args = func_get_args(); + print drupal_to_js( + array( + 'success' => TRUE, + 'content' => array_pop($args), + ) + ); + exit(); +} Index: modules/system/system.admin.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.admin.inc,v retrieving revision 1.74 diff -u -p -r1.74 system.admin.inc --- modules/system/system.admin.inc 5 May 2008 21:10:48 -0000 1.74 +++ modules/system/system.admin.inc 8 May 2008 12:57:53 -0000 @@ -1866,7 +1866,7 @@ function system_batch_page() { elseif (isset($output)) { // Force a page without blocks or messages to // display a list of collected messages later. - print theme('page', $output, FALSE, FALSE); + return array('page', $output, FALSE, FALSE); } } Index: modules/system/system.module =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.module,v retrieving revision 1.601 diff -u -p -r1.601 system.module --- modules/system/system.module 6 May 2008 12:18:50 -0000 1.601 +++ modules/system/system.module 8 May 2008 12:57:56 -0000 @@ -661,6 +661,34 @@ function _system_themes_access($theme) { } /** + * Implementation of hook_renderer(). + * + * If a page or item has been requested via jQuery, return it in JSON format. + */ +function system_renderer($hook, &$variables) { + global $theme_key; + + // jQuery sets a HTTP_X_REQUESTED_WITH header of 'XMLHttpRequest'. + if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') { + // If this is a full page request that has reached the rendering stage, + // return it in JSON. + if ($hook == 'page') { + // Strip out all but a subset of page properties. + // This prevents security issues involved with passing unthemed + // data to the client and minimizes transfer bandwidth. + $use_variables = array('content', 'tabs', 'title', 'show_blocks', 'show_messages', 'menu_result', 'sidebar_left', 'sidebar_right', 'help', 'messages', 'mission', 'primary_links', 'secondary_links'); + $use_variables = array_merge($use_variables, array_keys(system_region_list($theme_key))); + foreach (array_keys($variables) as $key) { + if (!in_array($key, $use_variables)) { + unset($variables[$key]); + } + } + return 'drupal_render_js'; + } + } +} + +/** * Implementation of hook_init(). */ function system_init() {