Index: index.php =================================================================== RCS file: /cvs/drupal/drupal/index.php,v retrieving revision 1.93 diff -u -p -r1.93 index.php --- index.php 6 Apr 2007 13:27:20 -0000 1.93 +++ index.php 20 May 2007 23:28:04 -0000 @@ -12,25 +12,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_footer(); +drupal_page_content(); +drupal_page_footer(); \ No newline at end of file Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.641 diff -u -p -r1.641 common.inc --- includes/common.inc 15 May 2007 20:19:47 -0000 1.641 +++ includes/common.inc 20 May 2007 23:28:10 -0000 @@ -322,8 +322,8 @@ function drupal_goto($path = '', $query function drupal_site_offline() { drupal_set_header('HTTP/1.1 503 Service unavailable'); drupal_set_title(t('Site off-line')); - print theme('maintenance_page', filter_xss_admin(variable_get('site_offline_message', - t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal')))))); + return array('maintenance_page', filter_xss_admin(variable_get('site_offline_message', + t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal'))))), NULL); } /** @@ -353,8 +353,8 @@ function drupal_not_found() { drupal_set_title(t('Page not found')); $return = ''; } - // To conserve CPU and bandwidth, omit the blocks - print theme('page', $return, FALSE); + + return array('page', $return, FALSE); } /** @@ -383,7 +383,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, FALSE); } /** @@ -1335,6 +1335,78 @@ function l($text, $path, $options = arra } /** + * Return an array representing the rendered page for use e.g. in Javascript + * applications. + * + * @return + * An array of page elements. + */ +function drupal_page($content) { + $return = array(); + switch ($content) { + case MENU_NOT_FOUND: + case MENU_ACCESS_DENIED: + case MENU_SITE_OFFLINE: + $return['status'] = FALSE; + $return['page'] = $content; + break; + default: + init_theme(); + $hooks = theme_get_registry(); + if (!isset($hooks['page'])) { + $return['status'] = FALSE; + break; + } + $return['status'] = TRUE; + $info = $hooks['page']; + $variables = array('content' => $content); + if (isset($info['preprocess functions']) && is_array($info['preprocess functions'])) { + // This construct ensures that we can 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['page'] = $variables; + break; + } + return $return; +} + +/** + * Call the active menu handler and output the result. + */ +function drupal_page_content() { + $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: + 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; + } + } + // Print any value (including an empty string) except NULL or undefined: + elseif (isset($return)) { + list($hook, $content, $blocks) = array('page', $return, TRUE); + $return = TRUE; + } + if (isset($hook)) { + print theme($hook, $content, $blocks, TRUE, $return); + } +} + +/** * Perform end-of-request tasks. * * This function sets the page cache if appropriate, and allows modules to @@ -1349,6 +1421,24 @@ function drupal_page_footer() { } /** + * 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; + } + } +} + +/** * Form an associative array from a linear array. * * This function walks through the provided array and constructs an associative @@ -1787,6 +1877,21 @@ function drupal_to_js($var) { } /** + * 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( + 'status' => TRUE, + 'content' => array_pop($args), + )); + exit(); +} + +/** * Wrapper around urlencode() which avoids Apache quirks. * * Should be used when placing arbitrary data in an URL. Note that Drupal paths @@ -2332,7 +2437,7 @@ function drupal_common_themes() { 'arguments' => array('text' => NULL) ), 'page' => array( - 'arguments' => array('content' => NULL, 'show_blocks' => TRUE, 'show_messages' => TRUE), + 'arguments' => array('content' => NULL, 'show_blocks' => TRUE, 'show_messages' => TRUE, 'menu_result' => TRUE), 'file' => 'page', ), 'maintenance_page' => array( Index: includes/theme.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/theme.inc,v retrieving revision 1.355 diff -u -p -r1.355 theme.inc --- includes/theme.inc 6 May 2007 05:47:51 -0000 1.355 +++ includes/theme.inc 20 May 2007 23:28:14 -0000 @@ -472,6 +472,9 @@ function theme() { } include_once($function_file); } + // Enable the redirection of output into formats other than XHTML, + // e.g. XML, JSON. + drupal_set_renderer($info['function'], $hook, $args); $output = call_user_func_array($info['function'], $args); } else { @@ -541,6 +544,9 @@ function theme() { $template_file = $hooks[$hook]['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() Index: modules/system/system.module =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.module,v retrieving revision 1.478 diff -u -p -r1.478 system.module --- modules/system/system.module 17 May 2007 07:28:42 -0000 1.478 +++ modules/system/system.module 20 May 2007 23:28:21 -0000 @@ -341,6 +341,34 @@ function system_menu() { return $items; } +/** + * 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'; + } + } +} + function system_init() { // Use the administrative theme if the user is looking at a page in the admin/* path. if (arg(0) == 'admin') {