summaryrefslogtreecommitdiff
path: root/includes
diff options
context:
space:
mode:
authorDries Buytaert <dries@buytaert.net>2009-10-15 14:07:30 +0000
committerDries Buytaert <dries@buytaert.net>2009-10-15 14:07:30 +0000
commitf42bca3bd4a7b97e103ddba3fdb4e403f7215b2a (patch)
tree771d33dbec4ead4d7a3a20022dbbdd669bd32691 /includes
parentdbac31e066a95983c6a20d9cf9c69f048bb12ead (diff)
downloadbrdo-f42bca3bd4a7b97e103ddba3fdb4e403f7215b2a.tar.gz
brdo-f42bca3bd4a7b97e103ddba3fdb4e403f7215b2a.tar.bz2
- Patch #599804 by effulgentsia, catch: unify page, AJAX 'path', and AJAX 'callback' callbacks. Oh my, this is the beginning of something big.
Diffstat (limited to 'includes')
-rw-r--r--includes/ajax.inc72
-rw-r--r--includes/common.inc254
-rw-r--r--includes/menu.inc68
3 files changed, 300 insertions, 94 deletions
diff --git a/includes/ajax.inc b/includes/ajax.inc
index ca198d6fa..0cd6f63e6 100644
--- a/includes/ajax.inc
+++ b/includes/ajax.inc
@@ -291,30 +291,60 @@ function ajax_form_callback() {
$callback = $triggering_element['#ajax']['callback'];
}
if (!empty($callback) && function_exists($callback)) {
- $html = $callback($form, $form_state);
-
- // If the returned value is a string, assume it is HTML, add the status
- // messages, and create a command object to return automatically. We want
- // the status messages inside the new wrapper, so that they get replaced
- // on subsequent AJAX calls for the same wrapper.
- if (is_string($html)) {
- $commands = array();
- $commands[] = ajax_command_replace(NULL, $html);
- $commands[] = ajax_command_prepend(NULL, theme('status_messages'));
- }
- // Otherwise, $html is supposed to be an array of commands, suitable for
- // Drupal.ajax, so we pass it on as is. In this situation, the callback is
- // doing something fancy, so let it decide how to handle status messages
- // without second guessing it.
- else {
- $commands = $html;
- }
+ return $callback($form, $form_state);
+ }
+}
- ajax_render($commands);
+/**
+ * Package and send the result of a page callback to the browser as an AJAX response.
+ *
+ * @param $page_callback_result
+ * The result of a page callback. Can be one of:
+ * - NULL: to indicate no content.
+ * - An integer menu status constant: to indicate an error condition.
+ * - A string of HTML content.
+ * - A renderable array of content.
+ */
+function ajax_deliver($page_callback_result) {
+ $commands = array();
+ if (!isset($page_callback_result)) {
+ // Simply delivering an empty commands array is sufficient. This results
+ // in the AJAX request being completed, but nothing being done to the page.
}
+ elseif (is_int($page_callback_result)) {
+ switch ($page_callback_result) {
+ case MENU_NOT_FOUND:
+ $commands[] = ajax_command_alert(t('The requested page could not be found.'));
+ break;
+
+ case MENU_ACCESS_DENIED:
+ $commands[] = ajax_command_alert(t('You are not authorized to access this page.'));
+ break;
- // Return a 'do nothing' command if there was no callback.
- ajax_render(array());
+ case MENU_SITE_OFFLINE:
+ $commands[] = ajax_command_alert(filter_xss_admin(variable_get('maintenance_mode_message',
+ t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal'))))));
+ break;
+ }
+ }
+ elseif (is_array($page_callback_result) && isset($page_callback_result['#type']) && ($page_callback_result['#type'] == 'ajax_commands')) {
+ // Complex AJAX callbacks can return a result that contains a specific
+ // set of commands to send to the browser.
+ if (isset($page_callback_result['#ajax_commands'])) {
+ $commands = $page_callback_result['#ajax_commands'];
+ }
+ }
+ else {
+ // Like normal page callbacks, simple AJAX callbacks can return html
+ // content, as a string or renderable array, to replace what was previously
+ // there in the wrapper. In this case, in addition to the content, we want
+ // to add the status messages, but inside the new wrapper, so that they get
+ // replaced on subsequent AJAX calls for the same wrapper.
+ $html = is_string($page_callback_result) ? $page_callback_result : drupal_render($page_callback_result);
+ $commands[] = ajax_command_replace(NULL, $html);
+ $commands[] = ajax_command_prepend(NULL, theme('status_messages'));
+ }
+ ajax_render($commands);
}
/**
diff --git a/includes/common.inc b/includes/common.inc
index e715010f2..9bc5e335b 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -665,75 +665,39 @@ function drupal_goto($path = '', array $query = array(), $fragment = NULL, $http
}
/**
- * Generates a site offline message.
+ * Deliver a "site is under maintenance" message to the browser.
+ *
+ * Page callback functions wanting to report a "site offline" message should
+ * return MENU_SITE_OFFLINE instead of calling drupal_site_offline(). However,
+ * functions that are invoked in contexts where that return value might not
+ * bubble up to menu_execute_active_handler() should call drupal_site_offline().
*/
function drupal_site_offline() {
- drupal_maintenance_theme();
- drupal_add_http_header('503 Service unavailable');
- drupal_set_title(t('Site under maintenance'));
- print theme('maintenance_page', array('content' => filter_xss_admin(variable_get('maintenance_mode_message',
- t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal')))))));
+ drupal_deliver_page(MENU_SITE_OFFLINE);
}
/**
- * Generates a 404 error if the request can not be handled.
+ * Deliver a "page not found" error to the browser.
+ *
+ * Page callback functions wanting to report a "page not found" message should
+ * return MENU_NOT_FOUND instead of calling drupal_not_found(). However,
+ * functions that are invoked in contexts where that return value might not
+ * bubble up to menu_execute_active_handler() should call drupal_not_found().
*/
function drupal_not_found() {
- drupal_add_http_header('404 Not Found');
-
- watchdog('page not found', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
-
- // Keep old path for reference, and to allow forms to redirect to it.
- if (!isset($_GET['destination'])) {
- $_GET['destination'] = $_GET['q'];
- }
-
- $path = drupal_get_normal_path(variable_get('site_404', ''));
- if ($path && $path != $_GET['q']) {
- // Custom 404 handler. Set the active item in case there are tabs to
- // display, or other dependencies on the path.
- menu_set_active_item($path);
- $return = menu_execute_active_handler($path);
- }
-
- if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) {
- // Standard 404 handler.
- drupal_set_title(t('Page not found'));
- $return = t('The requested page could not be found.');
- }
-
- drupal_set_page_content($return);
- $page = element_info('page');
- print drupal_render_page($page);
+ drupal_deliver_page(MENU_NOT_FOUND);
}
/**
- * Generates a 403 error if the request is not allowed.
+ * Deliver a "access denied" error to the browser.
+ *
+ * Page callback functions wanting to report an "access denied" message should
+ * return MENU_ACCESS_DENIED instead of calling drupal_access_denied(). However,
+ * functions that are invoked in contexts where that return value might not
+ * bubble up to menu_execute_active_handler() should call drupal_access_denied().
*/
function drupal_access_denied() {
- drupal_add_http_header('403 Forbidden');
- watchdog('access denied', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
-
- // Keep old path for reference, and to allow forms to redirect to it.
- if (!isset($_GET['destination'])) {
- $_GET['destination'] = $_GET['q'];
- }
-
- $path = drupal_get_normal_path(variable_get('site_403', ''));
- if ($path && $path != $_GET['q']) {
- // Custom 403 handler. Set the active item in case there are tabs to
- // display or other dependencies on the path.
- menu_set_active_item($path);
- $return = menu_execute_active_handler($path);
- }
-
- if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) {
- // Standard 403 handler.
- drupal_set_title(t('Access denied'));
- $return = t('You are not authorized to access this page.');
- }
-
- print drupal_render_page($return);
+ drupal_deliver_page(MENU_ACCESS_DENIED);
}
/**
@@ -2592,6 +2556,182 @@ function l($text, $path, array $options = array()) {
}
/**
+ * Deliver a page callback result to the browser in the format appropriate.
+ *
+ * This function is most commonly called by menu_execute_active_handler(), but
+ * can also be called by error conditions such as drupal_not_found(),
+ * drupal_access_denied(), and drupal_site_offline().
+ *
+ * When a user requests a page, index.php calls menu_execute_active_handler()
+ * which calls the 'page callback' function registered in hook_menu(). The page
+ * callback function can return one of:
+ * - NULL: to indicate no content
+ * - an integer menu status constant: to indicate an error condition
+ * - a string of HTML content
+ * - a renderable array of content
+ * Returning a renderable array rather than a string of HTML is preferred,
+ * because that provides modules with more flexibility in customizing the final
+ * result.
+ *
+ * When the page callback returns its constructed content to
+ * menu_execute_active_handler(), this functions gets called. The purpose of
+ * this function is to determine the most appropriate 'delivery callback'
+ * function to route the content to. The delivery callback function then
+ * sends the content to the browser in the needed format. The default delivery
+ * callback is drupal_deliver_html_page() which delivers the content as an HTML
+ * page, complete with blocks in addition to the content. This default can be
+ * overridden on a per menu item basis by setting 'delivery callback' in
+ * hook_menu(), hook_menu_alter(), or hook_menu_active_handler_alter().
+ * Additionally, modules may use hook_page_delivery_callback_alter() to specify
+ * a different delivery callback to use for the page request.
+ *
+ * For example, the same page callback function can be used for an HTML
+ * version of the page and an AJAX version of the page. The page callback
+ * function just needs to decide what content is to be returned and the
+ * delivery callback function will send it as an HTML page or an AJAX
+ * response, as appropriate.
+ *
+ * In order for page callbacks to be reusable in different delivery formats,
+ * they should not issue any "print" or "echo" statements, but instead just
+ * return content.
+ *
+ * @param $page_callback_result
+ * The result of a page callback. Can be one of:
+ * - NULL: to indicate no content.
+ * - An integer menu status constant: to indicate an error condition.
+ * - A string of HTML content.
+ * - A renderable array of content.
+ * @param $default_delivery_callback
+ * (Optional) If given, it is the name of a delivery function most likely
+ * to be appropriate for the page request as determined by the calling
+ * function (e.g., menu_execute_active_handler()). If not given, it is
+ * determined from the menu router information of the current page. In either
+ * case, modules have a final chance to alter which function is called.
+ *
+ * @see menu_execute_active_handler()
+ * @see hook_menu()
+ * @see hook_menu_alter()
+ * @see hook_menu_active_handler_alter()
+ * @see hook_page_delivery_callback_alter()
+ */
+function drupal_deliver_page($page_callback_result, $default_delivery_callback = NULL) {
+ if (!isset($default_delivery_callback) && ($router_item = menu_get_item())) {
+ drupal_alter('menu_active_handler', $router_item);
+ $default_delivery_callback = $router_item['delivery_callback'];
+ }
+ $delivery_callback = !empty($default_delivery_callback) ? $default_delivery_callback : 'drupal_deliver_html_page';
+ // Give modules a final chance to alter the delivery callback used. This is
+ // for modules that need to decide which delivery callback to use based on
+ // information made available during page callback execution and for pages
+ // without router items.
+ drupal_alter('page_delivery_callback', $delivery_callback);
+ if (function_exists($delivery_callback)) {
+ $delivery_callback($page_callback_result);
+ }
+ else {
+ // If a delivery callback is specified, but doesn't exist as a function,
+ // something is wrong, but don't print anything, since it's not known
+ // what format the response needs to be in.
+ watchdog('delivery callback not found', check_plain($delivery_callback) . ': ' . check_plain($_GET['q']), NULL, WATCHDOG_ERROR);
+ }
+}
+
+/**
+ * Package and send the result of a page callback to the browser as a normal
+ * HTML page.
+ *
+ * @param $page_callback_result
+ * The result of a page callback. Can be one of:
+ * - NULL: to indicate no content.
+ * - An integer menu status constant: to indicate an error condition.
+ * - A string of HTML content.
+ * - A renderable array of content.
+ *
+ * @see drupal_deliver_page
+ */
+function drupal_deliver_html_page($page_callback_result) {
+ // Menu status constants are integers; page content is a string or array.
+ if (is_int($page_callback_result)) {
+ // @todo: Break these up into separate functions?
+ switch ($page_callback_result) {
+ case MENU_NOT_FOUND:
+ // Print a 404 page.
+ drupal_add_http_header('404 Not Found');
+
+ watchdog('page not found', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
+
+ // Keep old path for reference, and to allow forms to redirect to it.
+ if (!isset($_GET['destination'])) {
+ $_GET['destination'] = $_GET['q'];
+ }
+
+ $path = drupal_get_normal_path(variable_get('site_404', ''));
+ if ($path && $path != $_GET['q']) {
+ // Custom 404 handler. Set the active item in case there are tabs to
+ // display, or other dependencies on the path.
+ menu_set_active_item($path);
+ $return = menu_execute_active_handler($path, FALSE);
+ }
+
+ if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) {
+ // Standard 404 handler.
+ drupal_set_title(t('Page not found'));
+ $return = t('The requested page could not be found.');
+ }
+
+ drupal_set_page_content($return);
+ $page = element_info('page');
+ print drupal_render_page($page);
+ break;
+
+ case MENU_ACCESS_DENIED:
+ // Print a 403 page.
+ drupal_add_http_header('403 Forbidden');
+ watchdog('access denied', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
+
+ // Keep old path for reference, and to allow forms to redirect to it.
+ if (!isset($_GET['destination'])) {
+ $_GET['destination'] = $_GET['q'];
+ }
+
+ $path = drupal_get_normal_path(variable_get('site_403', ''));
+ if ($path && $path != $_GET['q']) {
+ // Custom 403 handler. Set the active item in case there are tabs to
+ // display or other dependencies on the path.
+ menu_set_active_item($path);
+ $return = menu_execute_active_handler($path, FALSE);
+ }
+
+ if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) {
+ // Standard 403 handler.
+ drupal_set_title(t('Access denied'));
+ $return = t('You are not authorized to access this page.');
+ }
+
+ print drupal_render_page($return);
+ break;
+
+ case MENU_SITE_OFFLINE:
+ // Print a 503 page.
+ drupal_maintenance_theme();
+ drupal_add_http_header('503 Service unavailable');
+ drupal_set_title(t('Site under maintenance'));
+ print theme('maintenance_page', array('content' => filter_xss_admin(variable_get('maintenance_mode_message',
+ t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal')))))));
+ break;
+ }
+ }
+ elseif (isset($page_callback_result)) {
+ // Print anything besides a menu constant, assuming it's not NULL or
+ // undefined.
+ print drupal_render_page($page_callback_result);
+ }
+
+ // Perform end-of-request tasks.
+ drupal_page_footer();
+}
+
+/**
* Perform end-of-request tasks.
*
* This function sets the page cache if appropriate, and allows modules to
diff --git a/includes/menu.inc b/includes/menu.inc
index f14b7aa6b..05cdf7771 100644
--- a/includes/menu.inc
+++ b/includes/menu.inc
@@ -401,29 +401,56 @@ function menu_get_item($path = NULL, $router_item = NULL) {
}
/**
- * Execute the page callback associated with the current path
- */
-function menu_execute_active_handler($path = NULL) {
+ * Execute the page callback associated with the current path.
+ *
+ * @param $path
+ * The drupal path whose handler is to be be executed. If set to NULL, then
+ * the current path is used.
+ * @param $deliver
+ * (optional) A boolean to indicate whether the content should be sent to the
+ * browser using the appropriate delivery callback (TRUE) or whether to return
+ * the result to the caller (FALSE).
+ */
+function menu_execute_active_handler($path = NULL, $deliver = TRUE) {
if (_menu_site_is_offline()) {
- return MENU_SITE_OFFLINE;
+ $page_callback_result = MENU_SITE_OFFLINE;
}
- // Rebuild if we know it's needed, or if the menu masks are missing which
- // occurs rarely, likely due to a race condition of multiple rebuilds.
- if (variable_get('menu_rebuild_needed', FALSE) || !variable_get('menu_masks', array())) {
- menu_rebuild();
- }
- if ($router_item = menu_get_item($path)) {
- if ($router_item['access']) {
- if ($router_item['file']) {
- require_once DRUPAL_ROOT . '/' . $router_item['file'];
+ else {
+ // Rebuild if we know it's needed, or if the menu masks are missing which
+ // occurs rarely, likely due to a race condition of multiple rebuilds.
+ if (variable_get('menu_rebuild_needed', FALSE) || !variable_get('menu_masks', array())) {
+ menu_rebuild();
+ }
+ if ($router_item = menu_get_item($path)) {
+ // hook_menu_alter() lets modules control menu router information that
+ // doesn't depend on the details of a particular page request.
+ // Here, we want to give modules a chance to use request-time information
+ // to make alterations just for this request.
+ drupal_alter('menu_active_handler', $router_item, $path);
+ if ($router_item['access']) {
+ if ($router_item['file']) {
+ require_once DRUPAL_ROOT . '/' . $router_item['file'];
+ }
+ $page_callback_result = call_user_func_array($router_item['page_callback'], $router_item['page_arguments']);
+ }
+ else {
+ $page_callback_result = MENU_ACCESS_DENIED;
}
- return call_user_func_array($router_item['page_callback'], $router_item['page_arguments']);
}
else {
- return MENU_ACCESS_DENIED;
+ $page_callback_result = MENU_NOT_FOUND;
}
}
- return MENU_NOT_FOUND;
+
+ // Deliver the result of the page callback to the browser, or if requested,
+ // return it raw, so calling code can do more processing.
+ if ($deliver) {
+ $default_delivery_callback = (isset($router_item) && $router_item) ? $router_item['delivery_callback'] : NULL;
+ drupal_deliver_page($page_callback_result, $default_delivery_callback);
+ }
+ else {
+ return $page_callback_result;
+ }
}
/**
@@ -935,6 +962,7 @@ function menu_tree_all_data($menu_name, $link = NULL, $max_depth = NULL) {
'access_arguments',
'page_callback',
'page_arguments',
+ 'delivery_callback',
'title',
'title_callback',
'title_arguments',
@@ -1118,6 +1146,7 @@ function menu_tree_page_data($menu_name, $max_depth = NULL) {
'access_arguments',
'page_callback',
'page_arguments',
+ 'delivery_callback',
'title',
'title_callback',
'title_arguments',
@@ -2833,6 +2862,10 @@ function _menu_router_build($callbacks) {
$item['file path'] = $parent['file path'];
}
}
+ // Same for delivery callbacks.
+ if (!isset($item['delivery callback']) && isset($parent['delivery callback'])) {
+ $item['delivery callback'] = $parent['delivery callback'];
+ }
// Same for theme callbacks.
if (!isset($item['theme callback']) && isset($parent['theme callback'])) {
$item['theme callback'] = $parent['theme callback'];
@@ -2858,6 +2891,7 @@ function _menu_router_build($callbacks) {
'access callback' => '',
'page arguments' => array(),
'page callback' => '',
+ 'delivery callback' => '',
'block callback' => '',
'title arguments' => array(),
'title callback' => 't',
@@ -2905,6 +2939,7 @@ function _menu_router_save($menu, $masks) {
'access_arguments',
'page_callback',
'page_arguments',
+ 'delivery_callback',
'fit',
'number_parts',
'tab_parent',
@@ -2932,6 +2967,7 @@ function _menu_router_save($menu, $masks) {
'access_arguments' => serialize($item['access arguments']),
'page_callback' => $item['page callback'],
'page_arguments' => serialize($item['page arguments']),
+ 'delivery_callback' => $item['delivery callback'],
'fit' => $item['_fit'],
'number_parts' => $item['_number_parts'],
'tab_parent' => $item['tab_parent'],