drupal/modules/overlay/overlay.module

786 lines
26 KiB
Plaintext

<?php
// $Id$
/**
* @file
* Displays the Drupal administration interface in an overlay.
*/
/**
* Implements hook_help().
*/
function overlay_help($path, $arg) {
switch ($path) {
case 'admin/help#overlay':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The Overlay module makes the administration pages on your site display in a JavaScript overlay of the page you were viewing when you clicked the administrative link, instead of replacing the page in your browser window. Use the close link on the overlay to return to the page you were viewing when you clicked the link. For more information, see the online handbook entry for <a href="@overlay">Overlay module</a>.', array('@overlay' => 'http://drupal.org/handbook/modules/overlay')) . '</p>';
return $output;
}
}
/**
* Implements hook_menu().
*/
function overlay_menu() {
$items['overlay-ajax/%'] = array(
'title' => '',
'page callback' => 'overlay_ajax_render_region',
'page arguments' => array(1),
'access arguments' => array('access overlay'),
'type' => MENU_CALLBACK,
);
return $items;
}
/**
* Implements hook_permission().
*/
function overlay_permission() {
return array(
'access overlay' => array(
'title' => t('Access the administrative overlay'),
'description' => t('View administrative pages in the overlay.'),
),
);
}
/**
* Implements hook_init().
*
* Determine whether the current page request is destined to appear in the
* parent window or in the overlay window, and format the page accordingly.
*
* @see overlay_set_mode()
*/
function overlay_init() {
// @todo: custom_theme does not exist anymore.
global $custom_theme;
// Only act if the user has access to administration pages. Other modules can
// also enable the overlay directly for other uses of the JavaScript.
if (user_access('access overlay')) {
// After overlay is enabled on the modules page, redirect to
// <front>#overlay=admin/modules to actually enable the overlay.
if (isset($_SESSION['overlay_enable_redirect']) && $_SESSION['overlay_enable_redirect']) {
unset($_SESSION['overlay_enable_redirect']);
drupal_goto('<front>', array('fragment' => 'overlay=' . current_path()));
}
if (isset($_GET['render']) && $_GET['render'] == 'overlay') {
// If this page shouldn't be rendered here, redirect to the parent.
if (!path_is_admin($_GET['q'])) {
overlay_close_dialog();
}
// If system module did not switch the theme yet (i.e. this is not an
// admin page, per se), we should switch the theme here.
$admin_theme = variable_get('admin_theme', 0);
if ($custom_theme != $admin_theme) {
$custom_theme = $admin_theme;
drupal_add_css(drupal_get_path('module', 'system') . '/admin.css');
}
// Indicate that we are viewing an overlay child page.
overlay_set_mode('child');
// Unset the render parameter to avoid it being included in URLs on the page.
unset($_GET['render']);
}
// Do not enable the overlay if we already are on an admin page.
else if (!path_is_admin(current_path())) {
// Otherwise add overlay parent code and our behavior.
overlay_set_mode('parent');
}
}
}
/**
* Implements hook_exit().
*
* When viewing an overlay child page, check if we need to trigger a refresh of
* the supplemental regions of the overlay on the next page request.
*/
function overlay_exit() {
// Check that we are in an overlay child page. Note that this should never
// return TRUE on a cached page view, since the child mode is not set until
// overlay_init() is called.
if (overlay_get_mode() == 'child') {
// Load any markup that was stored earlier in the page request, via calls
// to overlay_store_rendered_content(). If none was stored, this is not a
// page request where we expect any changes to the overlay supplemental
// regions to have occurred, so we do not need to proceed any further.
$original_markup = overlay_get_rendered_content();
if (!empty($original_markup)) {
// Compare the original markup to the current markup that we get from
// rendering each overlay supplemental region now. If they don't match,
// something must have changed, so we request a refresh of that region
// within the parent window on the next page request.
foreach (overlay_supplemental_regions() as $region) {
if (!isset($original_markup[$region]) || $original_markup[$region] != overlay_render_region($region)) {
overlay_request_refresh($region);
}
}
}
}
}
/**
* Implements hook_element_info_alter().
*/
function overlay_element_info_alter(&$types) {
foreach (array('submit', 'button', 'image_button', 'form') as $type) {
$types[$type]['#after_build'][] = 'overlay_form_after_build';
}
}
/**
* Implements hook_library().
*/
function overlay_library() {
$module_path = drupal_get_path('module', 'overlay');
// Overlay parent.
$libraries['parent'] = array(
'title' => 'Overlay: Parent',
'website' => 'http://drupal.org/handbook/modules/overlay',
'version' => '1.0',
'js' => array(
$module_path . '/overlay-parent.js' => array(),
),
'css' => array(
$module_path . '/overlay-parent.css' => array(),
),
'dependencies' => array(
array('system', 'ui.dialog'),
array('system', 'ui.position'),
array('system', 'jquery-bbq'),
),
);
// Overlay child.
$libraries['child'] = array(
'title' => 'Overlay: Child',
'website' => 'http://drupal.org/handbook/modules/overlay',
'version' => '1.0',
'js' => array(
$module_path . '/overlay-child.js' => array(),
),
'dependencies' => array(
array('system', 'ui'),
),
);
return $libraries;
}
/**
* Implements hook_drupal_goto_alter().
*
* If the current page request is inside the overlay, add ?render=overlay to
* the new path, so that it appears correctly inside the overlay.
*
* @see overlay_get_mode()
*/
function overlay_drupal_goto_alter(&$path, &$options, &$http_response_code) {
if (overlay_get_mode() == 'child') {
if (isset($options['query'])) {
$options['query'] += array('render' => 'overlay');
}
else {
$options['query'] = array('render' => 'overlay');
}
}
}
/**
* Implements hook_batch_alter().
*
* If the current page request is inside the overlay, add ?render=overlay to
* the success callback URL, so that it appears correctly within the overlay.
*
* @see overlay_get_mode()
*/
function overlay_batch_alter(&$batch) {
if (overlay_get_mode() == 'child') {
if (isset($batch['url_options']['query'])) {
$batch['url_options']['query']['render'] = 'overlay';
}
else {
$batch['url_options']['query'] = array('render' => 'overlay');
}
}
}
/**
* Implements hook_page_alter().
*/
function overlay_page_alter(&$page) {
// If we are limiting rendering to a subset of page regions, deny access to
// all other regions so that they will not be processed.
if ($regions_to_render = overlay_get_regions_to_render()) {
$skipped_regions = array_diff(element_children($page), $regions_to_render);
foreach ($skipped_regions as $skipped_region) {
$page[$skipped_region]['#access'] = FALSE;
}
}
}
/**
* Implements hook_block_info_alter().
*/
function overlay_block_info_alter(&$blocks) {
// If we are limiting rendering to a subset of page regions, hide all blocks
// which appear in regions not on that list. Note that overlay_page_alter()
// does a more comprehensive job of preventing unwanted regions from being
// displayed (regardless of whether they contain blocks or not), but the
// reason for duplicating effort here is performance; we do not even want
// these blocks to be built if they are not going to be displayed.
if ($regions_to_render = overlay_get_regions_to_render()) {
foreach ($blocks as $bid => $block) {
if (!in_array($block->region, $regions_to_render)) {
unset($blocks[$bid]);
}
}
}
}
/**
* Implements hook_system_info_alter().
*
* Add default regions for the overlay.
*/
function overlay_system_info_alter(&$info, $file, $type) {
if ($type == 'theme') {
$info['overlay_regions'][] = 'content';
$info['overlay_regions'][] = 'help';
}
}
/**
* Preprocess template variables for html.tpl.php.
*
* If the current page request is inside the overlay, add appropriate classes
* to the <body> element, and simplify the page title.
*
* @see overlay_get_mode()
*/
function overlay_preprocess_html(&$variables) {
if (overlay_get_mode() == 'child') {
// Add overlay class, so themes can react to being displayed in the overlay.
$variables['classes_array'][] = 'overlay';
// Do not include site name or slogan in the overlay title.
$variables['head_title'] = drupal_get_title();
}
}
/**
* Preprocess template variables for maintenance-page.tpl.php.
*
* If the current page request is inside the overlay, add appropriate classes
* to the <body> element, and simplify the page title.
*
* @see overlay_preprocess_maintenance_page()
*/
function overlay_preprocess_maintenance_page(&$variables) {
overlay_preprocess_html($variables);
}
/**
* Preprocess template variables for page.tpl.php.
*
* Display breadcrumbs correctly inside the overlay.
*
* @see overlay_get_mode()
*/
function overlay_preprocess_page(&$variables) {
if (overlay_get_mode() == 'child') {
// Remove 'Home' from the breadcrumbs.
$overlay_breadcrumb = drupal_get_breadcrumb();
array_shift($overlay_breadcrumb);
$variables['breadcrumb'] = theme('breadcrumb', array('breadcrumb' => $overlay_breadcrumb));
}
}
/**
* Preprocess template variables for toolbar.tpl.php.
*
* Adding the 'overlay-displace-top' class to the toolbar pushes the overlay
* down, so it appears below the toolbar.
*/
function overlay_preprocess_toolbar(&$variables) {
$variables['classes_array'][] = "overlay-displace-top";
}
/**
* Form after_build callback.
*
* After all hook_form_alter() implementations have been processed, we look at
* the list of submit handlers and add our own at the end. The added handler
* determines whether or not the user is redirected done at the end of form
* processing, so that it's possible to close the overlay after submitting
* a form.
*
* @see form_execute_handlers()
* @see form_builder()
* @see overlay_form_submit()
*
* @ingroup forms
*/
function overlay_form_after_build($form, &$form_state) {
if (overlay_get_mode() == 'child') {
// If this element has submit handlers, then append our own.
if (isset($form['#submit'])) {
$form['#submit'][] = 'overlay_form_submit';
}
}
return $form;
}
/**
* Generic form submit handler.
*
* When we are requested to close an overlay, we don't want Form API to
* perform any redirection once the submitted form has been processed.
*
* When $form_state['redirect'] is set to FALSE, then Form API will simply
* re-render the form with the values still in its fields. And this is all
* we need to output the JavaScript that will tell the parent window to close
* the child dialog.
*
* @see overlay_get_mode()
* @ingroup forms
*/
function overlay_form_submit($form, &$form_state) {
$settings = &drupal_static(__FUNCTION__);
// Check if we have a request to close the overlay.
$args = overlay_request_dialog_close();
// Close the overlay if the overlay module has been disabled
if (!module_exists('overlay')) {
$args = overlay_request_dialog_close(TRUE);
}
// If there is a form redirect to a non-admin page, close the overlay.
if (isset($form_state['redirect'])) {
// A destination set in the URL trumps $form_state['redirect'].
if (isset($_GET['destination'])) {
$url = $_GET['destination'];
$url_settings = array();
}
elseif (is_array($form_state['redirect'])) {
$url = $form_state['redirect'][0];
$url_settings = $form_state['redirect'][1];
}
else {
$url = $form_state['redirect'];
$url_settings = array();
}
if (!path_is_admin($url)) {
$args = overlay_request_dialog_close(TRUE);
}
}
// If the overlay is to be closed, pass that information through JavaScript.
if ($args !== FALSE) {
if (!isset($settings)) {
$settings = array(
'overlayChild' => array(
'closeOverlay' => TRUE,
'statusMessages' => theme('status_messages'),
'args' => $args,
),
);
// Tell the child window to perform the redirection when requested to.
if (!empty($form_state['redirect'])) {
$settings['overlayChild']['redirect'] = url($url, $settings);
}
drupal_add_js($settings, array('type' => 'setting'));
}
// Tell FAPI to redraw the form without redirection after all submit
// callbacks have been processed.
$form_state['redirect'] = FALSE;
}
}
/**
* Get the current overlay mode.
*
* @see overlay_set_mode()
*/
function overlay_get_mode() {
return overlay_set_mode(NULL);
}
/**
* Set overlay mode and add proper JavaScript and styles to the page.
*
* @param $mode
* To set the mode, pass in either 'parent' or 'child'. 'parent' is used in
* the context of a parent window (a regular browser window), and JavaScript
* is added so that administrative links in the parent window will open in
* an overlay. 'child' is used in the context of the child overlay window (the
* page actually appearing within the overlay iframe) and JavaScript and CSS
* are added so that Drupal behaves nicely from within the overlay.
*
* This parameter is optional, and if omitted, the current mode will be
* returned with no action taken.
*
* @return
* The current mode, if any has been set, or NULL if no mode has been set.
*
* @ingroup overlay_api
*/
function overlay_set_mode($mode = NULL) {
global $base_path;
$overlay_mode = &drupal_static(__FUNCTION__);
// Make sure external resources are not included more than once. Also return
// the current mode, if no mode was specified.
if (isset($overlay_mode) || !isset($mode)) {
return $overlay_mode;
}
$overlay_mode = $mode;
switch ($overlay_mode) {
case 'parent':
drupal_add_library('overlay', 'parent');
drupal_add_library('overlay', 'jquery-bbq');
// Allow modules to act upon overlay events.
module_invoke_all('overlay_parent_initialize');
break;
case 'child':
drupal_add_library('overlay', 'child');
// Pass child's document height on to parent document as quickly as
// possible so it can be updated accordingly.
drupal_add_js('if (parent.Drupal && parent.Drupal.overlay) { parent.Drupal.overlay.innerResize(jQuery(document.body).outerHeight()); }', array('type' => 'inline', 'scope' => 'footer'));
// Allow modules to act upon overlay events.
module_invoke_all('overlay_child_initialize');
break;
}
return $overlay_mode;
}
/**
* Implements hook_overlay_parent_initialize().
*/
function overlay_overlay_parent_initialize() {
// Let the client side know which paths are administrative.
$paths = path_get_admin_paths();
foreach ($paths as &$type) {
$type = str_replace('<front>', variable_get('site_frontpage', 'node'), $type);
}
drupal_add_js(array('overlay' => array('paths' => $paths)), 'setting');
// Pass along the AJAX callback for rerendering sections of the parent window.
drupal_add_js(array('overlay' => array('ajaxCallback' => 'overlay-ajax')), 'setting');
}
/**
* Implements hook_overlay_child_initialize().
*/
function overlay_overlay_child_initialize() {
// Check if the parent window needs to refresh any page regions on this page
// request.
overlay_trigger_regions_to_refresh();
// If this is a POST request, or a GET request with a token parameter, we
// have an indication that something in the supplemental regions of the
// overlay might change during the current page request. We therefore store
// the initial rendered content of those regions here, so that we can compare
// it to the same content rendered in overlay_exit(), at the end of the page
// request. This allows us to check if anything actually did change, and, if
// so, trigger an AJAX refresh of the parent window.
if (!empty($_POST) || isset($_GET['token'])) {
foreach (overlay_supplemental_regions() as $region) {
overlay_store_rendered_content($region, overlay_render_region($region));
}
}
// Indicate that when the main page rendering occurs later in the page
// request, only the regions that appear within the overlay should be
// rendered.
overlay_set_regions_to_render(overlay_regions());
}
/**
* Callback to request that the overlay close on the next page load.
*
* @param $value
* By default, the dialog will not close. Set to TRUE or a value evaluating to
* TRUE to request the dialog to close. Use FALSE to disable closing the
* dialog (if it was previously enabled). The value passed will be forwarded
* to the onOverlayClose callback of the overlay.
*
* @return
* The current overlay close dialog mode, a value evaluating to TRUE if the
* overlay should close or FALSE if it should not (default).
*/
function overlay_request_dialog_close($value = NULL) {
$close = &drupal_static(__FUNCTION__, FALSE);
if (isset($value)) {
$close = $value;
}
return $close;
}
/**
* Close the overlay and redirect the parent window to a new path.
*
* @param $redirect
* The path that should open in the parent window after the overlay closes.
*/
function overlay_close_dialog($redirect = NULL) {
if (!isset($redirect)) {
$redirect = current_path();
}
$settings = array(
'overlayChild' => array(
'closeOverlay' => TRUE,
'statusMessages' => theme('status_messages'),
'args' => $args,
'redirect' => url($redirect),
),
);
drupal_add_js($settings, array('type' => 'setting'));
return $settings;
}
/**
* Returns a list of page regions that appear in the overlay.
*
* Overlay regions correspond to the entire contents of the overlay child
* window and are refreshed each time a new page request is made within the
* overlay.
*
* @return
* An array of region names that correspond to those which appear in the
* overlay, within the theme that is being used to display the current page.
*
* @see overlay_supplemental_regions()
*/
function overlay_regions() {
return _overlay_region_list('overlay_regions');
}
/**
* Returns a list of supplemental page regions for the overlay.
*
* Supplemental overlay regions are those which are technically part of the
* parent window, but appear to the user as being related to the overlay
* (usually because they are displayed next to, rather than underneath, the
* main overlay regions) and therefore need to be dynamically refreshed if any
* administrative actions taken within the overlay change their contents.
*
* An example of a typical overlay supplemental region would be the 'page_top'
* region, in the case where a toolbar is being displayed there.
*
* @return
* An array of region names that correspond to supplemental overlay regions,
* within the theme that is being used to display the current page.
*
* @see overlay_regions()
*/
function overlay_supplemental_regions() {
return _overlay_region_list('overlay_supplemental_regions');
}
/**
* Helper function for returning a list of page regions related to the overlay.
*
* @param $type
* The type of regions to return. This can either be 'overlay_regions' or
* 'overlay_supplemental_regions'.
*
* @return
* An array of region names of the given type, within the theme that is being
* used to display the current page.
*
* @see overlay_regions()
* @see overlay_supplemental_regions()
*/
function _overlay_region_list($type) {
// Obtain the current theme. We need to first make sure the theme system is
// initialized, since this function can be called early in the page request.
drupal_theme_initialize();
$themes = list_themes();
$theme = $themes[$GLOBALS['theme']];
// Return the list of regions stored within the theme's info array, or an
// empty array if no regions of the appropriate type are defined.
return !empty($theme->info[$type]) ? $theme->info[$type] : array();
}
/**
* Returns a list of page regions that rendering should be limited to.
*
* @return
* An array containing the names of the regions that will be rendered when
* drupal_render_page() is called. If empty, then no limits will be imposed,
* and all regions of the page will be rendered.
*
* @see overlay_page_alter()
* @see overlay_block_info_alter()
* @see overlay_set_regions_to_render()
*/
function overlay_get_regions_to_render() {
return overlay_set_regions_to_render();
}
/**
* Sets the regions of the page that rendering will be limited to.
*
* @param $regions
* (Optional) An array containing the names of the regions that should be
* rendered when drupal_render_page() is called. Pass in an empty array to
* remove all limits and cause drupal_render_page() to render all page
* regions (the default behavior). If this parameter is omitted, no change
* will be made to the current list of regions to render.
*
* @return
* The current list of regions to render, or an empty array if the regions
* are not being limited.
*
* @see overlay_page_alter()
* @see overlay_block_info_alter()
* @see overlay_get_regions_to_render()
*/
function overlay_set_regions_to_render($regions = NULL) {
$regions_to_render = &drupal_static(__FUNCTION__, array());
if (isset($regions)) {
$regions_to_render = $regions;
}
return $regions_to_render;
}
/**
* Renders an individual page region.
*
* This function is primarily intended to be used for checking the content of
* supplemental overlay regions (e.g., a region containing a toolbar). Passing
* in a region that is intended to display the main page content is not
* supported; the region will be rendered by this function, but the main page
* content will not appear in it.
*
* @param $region
* The name of the page region that should be rendered.
*
* @return
* The rendered HTML of the provided region.
*/
function overlay_render_region($region) {
// Indicate the region that we will be rendering, so that other regions will
// be hidden by overlay_page_alter() and overlay_block_info_alter().
overlay_set_regions_to_render(array($region));
// Do what is necessary to force drupal_render_page() to only display HTML
// from the requested region. Specifically, declare that the main page
// content does not need to automatically be added to the page, and pass in
// a page array that has all theme functions removed (so that overall HTML
// for the page will not be added either).
$system_main_content_added = &drupal_static('system_main_content_added');
$system_main_content_added = TRUE;
$page = array(
'#type' => 'page',
'#theme' => NULL,
'#theme_wrappers' => array(),
);
$markup = drupal_render_page($page);
// Indicate that the main page content has not, in fact, been displayed, so
// that future calls to drupal_render_page() will be able to render it
// correctly.
$system_main_content_added = FALSE;
// Restore the original behavior of rendering all regions for the next time
// drupal_render_page() is called.
overlay_set_regions_to_render(array());
return $markup;
}
/**
* Returns any rendered content that was stored earlier in the page request.
*
* @return
* An array of all rendered HTML that was stored earlier in the page request,
* keyed by the identifier with which it was stored. If no content was
* stored, an empty array is returned.
*
* @see overlay_store_rendered_content()
*/
function overlay_get_rendered_content() {
return overlay_store_rendered_content();
}
/**
* Stores strings representing rendered HTML content.
*
* This function is used to keep a static cache of rendered content that can be
* referred to later in the page request.
*
* @param $id
* (Optional) An identifier for the content which is being stored, which will
* be used as an array key in the returned array. If omitted, no change will
* be made to the current stored data.
* @param $content
* (Optional) A string representing the rendered data to store. This only has
* an effect if $id is also provided.
*
* @return
* An array representing all data that is currently being stored, or an empty
* array if there is none.
*
* @see overlay_get_rendered_content()
*/
function overlay_store_rendered_content($id = NULL, $content = NULL) {
$rendered_content = &drupal_static(__FUNCTION__, array());
if (isset($id)) {
$rendered_content[$id] = $content;
}
return $rendered_content;
}
/**
* Request that the parent window refresh a particular page region.
*
* @param $region
* The name of the page region to refresh. The parent window will trigger a
* refresh of this region on the next page load.
*
* @see overlay_trigger_regions_to_refresh()
* @see Drupal.overlay.refreshRegions()
*/
function overlay_request_refresh($region) {
$class = drupal_region_class($region);
$_SESSION['overlay_regions_to_refresh'][] = array($class => $region);
}
/**
* Check if the parent window needs to refresh any regions on this page load.
*
* If the previous page load requested that any page regions be refreshed, pass
* that request via JavaScript to the child window, so it can in turn pass the
* request to the parent window.
*
* @see overlay_request_refresh()
* @see Drupal.overlay.refreshRegions()
*/
function overlay_trigger_regions_to_refresh() {
if (!empty($_SESSION['overlay_regions_to_refresh'])) {
$settings = array(
'overlayChild' => array(
'refreshRegions' => $_SESSION['overlay_regions_to_refresh'],
),
);
drupal_add_js($settings, array('type' => 'setting'));
unset($_SESSION['overlay_regions_to_refresh']);
}
}
/**
* Prints the markup obtained by rendering a single region of the page.
*
* This function is intended to be called via AJAX.
*
* @param $region
* The name of the page region to render.
*
* @see Drupal.overlay.refreshRegions()
*/
function overlay_ajax_render_region($region) {
print overlay_render_region($region);
}