Index: index.php =================================================================== RCS file: /Users/Shared/code/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 11 Aug 2008 18:47:14 -0000 @@ -21,19 +21,21 @@ $return = menu_execute_active_handler(); if (is_int($return)) { switch ($return) { case MENU_NOT_FOUND: - drupal_not_found(); + $elements = drupal_not_found(); break; case MENU_ACCESS_DENIED: - drupal_access_denied(); + $elements = drupal_access_denied(); break; case MENU_SITE_OFFLINE: - drupal_site_offline(); + $elements = drupal_site_offline(); break; } } elseif (isset($return)) { // Print any value (including an empty string) except NULL or undefined: - print theme('page', $return); + $elements = drupal_build_page($return); } +echo drupal_render_page($elements); + drupal_page_footer(); Index: includes/common.inc =================================================================== RCS file: /Users/Shared/code/drupal/includes/common.inc,v retrieving revision 1.781 diff -u -p -r1.781 common.inc --- includes/common.inc 2 Aug 2008 19:01:02 -0000 1.781 +++ includes/common.inc 11 Aug 2008 21:20:47 -0000 @@ -47,20 +47,18 @@ function drupal_set_content($region = NU * @param $region * A specified region to fetch content for. If NULL, all regions will be * returned. - * @param $delimiter - * Content to be inserted between exploded array elements. */ -function drupal_get_content($region = NULL, $delimiter = ' ') { +function drupal_get_content($region = NULL) { $content = drupal_set_content(); if (isset($region)) { if (isset($content[$region]) && is_array($content[$region])) { - return implode($delimiter, $content[$region]); + return $content[$region]; } } else { foreach (array_keys($content) as $region) { - if (is_array($content[$region])) { - $content[$region] = implode($delimiter, $content[$region]); + if (!is_array($content[$region])) { + $content[$region] = array(); } } return $content; @@ -331,8 +329,15 @@ function drupal_site_offline() { drupal_maintenance_theme(); drupal_set_header('HTTP/1.1 503 Service unavailable'); drupal_set_title(t('Site offline')); - 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')))))); + + $elements['page'] = array( + '#title' => drupal_get_title(), + '#theme' => 'maintenance_page', + '#content' => array( + '#markup' => 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 $elements; } /** @@ -358,11 +363,11 @@ function drupal_not_found() { if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) { drupal_set_title(t('Page not found')); - $return = t('The requested page could not be found.'); + $return = array('#markup' => t('The requested page could not be found.')); } // To conserve CPU and bandwidth, omit the blocks. - print theme('page', $return, FALSE); + return drupal_build_page($return, FALSE, TRUE, FALSE); } /** @@ -387,9 +392,122 @@ function drupal_access_denied() { if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) { drupal_set_title(t('Access denied')); - $return = t('You are not authorized to access this page.'); + $return = array('#markup' => t('You are not authorized to access this page.')); } - print theme('page', $return); + return drupal_build_page($return, TRUE, TRUE, FALSE); +} + +/** + * Build a structured array with page content and theme parameters. + */ +function drupal_build_page($content, $show_blocks = TRUE, $show_messages = TRUE, $success = TRUE) { + if (!is_array($content)) { + $content = array('#markup' => $content); + } + $elements = array( + '#title' => drupal_get_title(), + '#theme' => 'page', + '#show_blocks' => $show_blocks, + '#show_messages' => $show_messages, + '#success' => $success, + 'content' => $content, + ); + + global $theme; + + $regions = system_region_list($theme); + // Load all region content assigned via blocks. + foreach (array_keys($regions) as $region) { + if (!isset($elements[$region])) { + $elements[$region] = array(); + } + // Prevent left and right regions from rendering blocks when 'show_blocks' == FALSE. + if (!(!$show_blocks && ($region == 'left' || $region == 'right'))) { + $elements[$region]['blocks'] = array('#markup' => theme('blocks', $region)); + } + } + + // Alter any part of the page before it's rendered. + drupal_alter('page', $elements); + return page_elements_builder($elements); +} + +/** + * Adds any required properties non-form elements on the page. + * + * Similar to form_builder(). + */ +function page_elements_builder($elements) { + // Use element defaults. + if ((!empty($elements['#type'])) && ($info = _element_info($elements['#type']))) { + // Overlay $info onto $form, retaining preexisting keys in $form. + $elements += $info; + } + + // We start off assuming all elements are in the correct order. + $elements['#sorted'] = TRUE; + + // Recurse through all child elements. + $count = 0; + foreach (element_children($elements) as $key) { + // Assign a decimal placeholder weight to preserve original array order. + if (!isset($form[$key]['#weight'])) { + $elements[$key]['#weight'] = $count/1000; + } + else { + // If one of the child elements has a weight then we will need to sort + // later. + unset($elements['#sorted']); + } + if ($elements[$key]['#type'] != 'form') { + $elements[$key] = page_elements_builder($elements[$key]); + } + $count++; + } + return $elements; +} + +function drupal_render_page($elements) { + $render_type = ''; + if (isset($_GET['render'])) { + $render_type = $_GET['render']; + } + // jQuery sets a HTTP_X_REQUESTED_WITH header of 'XMLHttpRequest'. + if (!$render_type) { + // Set the render type based on the request header. + if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') { + $render_type = 'json'; + } + else { + $render_type = 'xhtml'; + } + } + switch ($render_type) { + case 'xhtml': + case 'html': + $render = 'drupal_render'; + break; + + default: + $render = 'drupal_' . $render_type . '_render'; + if (!drupal_function_exists($render)) { + $render = 'drupal_render'; + } + break; + } + + return $render($elements); +} + +/** + * Render the elements into simple XML. + */ +function drupal_xml_render($elements) { + return drupal_render($elements, 'theme_xml'); +} + +function render(&$element) { + echo drupal_render($element); } /** @@ -2068,7 +2186,7 @@ function drupal_get_js($scope = 'header' // with HTML 4, we need to comment out the CDATA-tag. $embed_prefix = "\n\n"; - + foreach ($javascript as $type => $data) { if (!$data) continue; @@ -2699,7 +2817,7 @@ function drupal_alter($type, &$data) { /** - * Renders HTML given a structured array tree. + * Renders HTML or other output given a structured array tree. * * Recursively iterates over each of the array elements, generating HTML code. * This function is usually called from within a another function, like @@ -2707,10 +2825,13 @@ function drupal_alter($type, &$data) { * * @param $elements * The structured array describing the data to be rendered. + * @param $callback + * Optional parameter defining which function to use to mediate conversion of + * each elemement to a string. Use the default, 'theme', for XHTML. * @return - * The rendered HTML. + * The rendered content (XHTML or other depending on $callback). */ -function drupal_render(&$elements) { +function drupal_render(&$elements, $callback = 'theme') { if (!isset($elements) || (isset($elements['#access']) && !$elements['#access'])) { return NULL; } @@ -2729,7 +2850,7 @@ function drupal_render(&$elements) { if (isset($elements['#pre_render'])) { foreach ($elements['#pre_render'] as $function) { if (drupal_function_exists($function)) { - $elements = $function($elements); + $elements = $function($elements, $callback); } } } @@ -2754,12 +2875,12 @@ function drupal_render(&$elements) { $elements['#markup'] = ''; } - $content = theme($elements['#theme'], $elements); + $content = $callback($elements['#theme'], $elements); } // Render each of the children using drupal_render and concatenate them. if (!isset($content) || $content === '') { foreach ($children as $key) { - $content .= drupal_render($elements[$key]); + $content .= drupal_render($elements[$key], $callback); } } } @@ -2769,7 +2890,8 @@ function drupal_render(&$elements) { // Until now, we rendered the children, here we render the element itself if (!isset($elements['#printed'])) { - $content = theme(!empty($elements['#type']) ? $elements['#type'] : 'markup', $elements); + // The default $callback is 'theme'. + $content = $callback(!empty($elements['#type']) ? $elements['#type'] : 'markup', $elements); $elements['#printed'] = TRUE; } @@ -2780,7 +2902,7 @@ function drupal_render(&$elements) { if (isset($elements['#post_render'])) { foreach ($elements['#post_render'] as $function) { if (drupal_function_exists($function)) { - $content = $function($content, $elements); + $content = $function($content, $elements, $callback); } } } @@ -2790,6 +2912,139 @@ function drupal_render(&$elements) { } } + +/** + * Renders elements of structured array tree, retaining the structure. + * + * Recursively iterates over each of the array elements, generating output. + * This function is usually called from within a another function. + * + * @param $elements + * The structured array describing the data to be rendered. + * @param $callback + * Optional parameter defining which funciton to use to mediate conversion of + * each elemement to a string. Use the default, 'theme', for HTML. + * @return + * The rendered HTML. + */ +function drupal_render_array(&$elements, $callback = 'theme') { + if (!isset($elements) || (isset($elements['#access']) && !$elements['#access'])) { + $elements = NULL; + } + + // If the default values for this element haven't been loaded yet, populate + // them. + if (!isset($elements['#defaults_loaded']) || !$elements['#defaults_loaded']) { + if ((!empty($elements['#type'])) && ($info = _element_info($elements['#type']))) { + $elements += $info; + } + } + + // Make any final changes to the element before it is rendered. This means + // that the $element or the children can be altered or corrected before the + // element is rendered into the final text. + if (isset($elements['#pre_render'])) { + foreach ($elements['#pre_render'] as $function) { + if (drupal_function_exists($function)) { + $elements = $function($elements, $callback); + } + } + } + + $content = ''; + // Either the elements did not go through form_builder or one of the children + // has a #weight. + if (!isset($elements['#sorted'])) { + uasort($elements, "element_sort"); + } + $elements += array('#title' => NULL, '#description' => NULL); + if (!isset($elements['#children'])) { + $children = element_children($elements); + /* Render all the children that use a theme function */ + if (!$children && isset($elements['#theme']) && empty($elements['#theme_used'])) { + $elements['#theme_used'] = TRUE; + + $previous = array(); + foreach (array('#type', '#prefix', '#suffix') as $key) { + $previous[$key] = isset($elements[$key]) ? $elements[$key] : NULL; + } + // If we rendered a single element, then we will skip the renderer. + $elements['#printed'] = TRUE; + $elements['#type'] = 'markup'; + $elements['#value'] = ''; + + unset($elements['#prefix'], $elements['#suffix']); + + $args[] = $elements['#theme']; + $args[] = $elements; + if (!empty($elements['#theme arguments'])) { + foreach($elements['#theme arguments'] as $value) { + $args[] = $value; + } + } + // The default $callback is 'theme'. + $content = call_user_func_array($callback, $args); + + foreach (array('#type', '#prefix', '#suffix') as $key) { + $elements[$key] = isset($previous[$key]) ? $previous[$key] : NULL; + } + } + // Render each of the children using drupal_render_array + if (!isset($content) || $content === '') { + foreach ($children as $key) { + drupal_render_array($elements[$key], $callback); + } + } + } + + // Until now, we rendered the children, here we render the element itself + if (!isset($elements['#printed']) && !$children) { + // The default $callback is 'theme'. + $content = $callback(!empty($elements['#type']) ? $elements['#type'] : 'markup', $elements); + $elements['#printed'] = TRUE; + } + + if (isset($content) && $content !== '') { + // Filter the outputted content and make any last changes before the + // content is sent to the browser. The changes are made on $content + // which allows the output'ed text to be filtered. + if (isset($elements['#post_render'])) { + foreach ($elements['#post_render'] as $function) { + if (drupal_function_exists($function)) { + $content = $function($content, $elements, $callback); + } + } + } + $prefix = isset($elements['#prefix']) ? $elements['#prefix'] : ''; + $suffix = isset($elements['#suffix']) ? $elements['#suffix'] : ''; + $elements['#value'] = $prefix . $content . $suffix; + } +} + +function element_property_preserve_in_array($key) { + switch ($key) { + case '#title': + case '#value': + case '#type': + return TRUE; + + default: + return FALSE; + } +} + +function drupal_json_render($elements) { + drupal_set_header('Content-Type: text/javascript; charset=utf-8'); + drupal_render_array($elements); + return drupal_to_js($elements); +} + +function drupal_printr_render($elements) { + drupal_set_header('Content-Type: text/plain; charset=utf-8'); + drupal_render_array($elements); + return print_r($elements, 1); +} + /** * Function used by uasort to sort structured arrays by weight. */ @@ -2840,18 +3095,18 @@ function drupal_common_theme() { 'arguments' => array('text' => NULL) ), 'page' => array( - 'arguments' => array('content' => NULL, 'show_blocks' => TRUE, 'show_messages' => TRUE), + 'arguments' => array('elements' => NULL, 'show_blocks' => TRUE, 'show_messages' => TRUE), 'template' => 'page', ), 'maintenance_page' => array( - 'arguments' => array('content' => NULL, 'show_blocks' => TRUE, 'show_messages' => TRUE), + 'arguments' => array('elements' => NULL, 'show_blocks' => TRUE, 'show_messages' => TRUE), 'template' => 'maintenance-page', ), 'update_page' => array( - 'arguments' => array('content' => NULL, 'show_messages' => TRUE), + 'arguments' => array('elements' => NULL, 'show_messages' => TRUE), ), 'install_page' => array( - 'arguments' => array('content' => NULL), + 'arguments' => array('elements' => NULL), ), 'task_list' => array( 'arguments' => array('items' => NULL, 'active' => NULL), Index: includes/form.inc =================================================================== RCS file: /Users/Shared/code/drupal/includes/form.inc,v retrieving revision 1.277 diff -u -p -r1.277 form.inc --- includes/form.inc 18 Jul 2008 07:06:24 -0000 1.277 +++ includes/form.inc 11 Aug 2008 20:04:22 -0000 @@ -143,9 +143,10 @@ function drupal_get_form($form_id) { $form = drupal_rebuild_form($form_id, $form_state, $args); } - // If we haven't redirected to a new location by now, we want to - // render whatever form array is currently in hand. - return drupal_render_form($form_id, $form); + // If we haven't redirected to a new location by now, we want to prepare for + // rendering whatever form array is currently in hand. + drupal_set_form_theme($form_id, $form); + return $form; } /** @@ -586,7 +587,7 @@ function drupal_validate_form($form_id, * A string containing the path of the page to display when processing * is complete. */ -function drupal_render_form($form_id, &$form) { +function drupal_set_form_theme($form_id, &$form) { // Don't override #theme if someone already set it. if (!isset($form['#theme'])) { init_theme(); @@ -595,9 +596,6 @@ function drupal_render_form($form_id, &$ $form['#theme'] = $form_id; } } - - $output = drupal_render($form); - return $output; } /** Index: includes/theme.inc =================================================================== RCS file: /Users/Shared/code/drupal/includes/theme.inc,v retrieving revision 1.431 diff -u -p -r1.431 theme.inc --- includes/theme.inc 2 Aug 2008 19:01:02 -0000 1.431 +++ includes/theme.inc 11 Aug 2008 20:18:06 -0000 @@ -476,6 +476,14 @@ function list_themes($refresh = FALSE) { } /** + * Dummy function for now. + */ +function theme_xml() { + $args = func_get_args(); + return call_user_func_array('theme', $args); +} + +/** * Generate the themed output. * * All requests for theme hooks must go through this function. It examines @@ -557,14 +565,14 @@ function list_themes($refresh = FALSE) { * so that if the specific theme hook isn't implemented anywhere, a more * generic one will be used. This can allow themes to create specific theme * implementations for named objects. - * @param ... - * Additional arguments to pass along to the theme function. + * @param $elements + * (optional) the data to be converted to html. Typically this is a structured + * array (such as a form element), but it may also be a scalar (e.g. string) + * for very simple theme functions like theme_placeholder(). * @return - * An HTML string that generates the themed output. + * An HTML string that contains the themed output. */ -function theme() { - $args = func_get_args(); - $hook = array_shift($args); +function theme($hook, $elements = array()) { static $hooks = NULL; if (!isset($hooks)) { @@ -601,20 +609,14 @@ function theme() { } if (isset($info['function'])) { // The theme call is a function. - $output = call_user_func_array($info['function'], $args); + $output = call_user_func($info['function'], $elements); } else { // The theme call is a template. $variables = array( 'template_files' => array() ); - if (!empty($info['arguments'])) { - $count = 0; - foreach ($info['arguments'] as $name => $default) { - $variables[$name] = isset($args[$count]) ? $args[$count] : $default; - $count++; - } - } + $variables['elements'] = $elements; // default render function and extension. $render_function = 'theme_render_template'; @@ -637,12 +639,9 @@ function theme() { } 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 (drupal_function_exists($preprocess_function)) { - call_user_func_array($preprocess_function, $args); + $preprocess_function($variables, $hook); } } } @@ -1097,8 +1096,10 @@ function theme_status_messages($display * @return * A string containing an unordered list of links. */ -function theme_links($links, $attributes = array('class' => 'links')) { +function theme_links($element) { $output = ''; + $links = $element['#links']; + $attributes = isset($element['#attributes']) ? $element['#attributes'] : array('class' => 'links'); if (count($links) > 0) { $output = '