'', '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')) { 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'); } else { // 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/node/517688', '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', 'jquery-bbq'), ), ); // Overlay child. $libraries['child'] = array( 'title' => 'Overlay: Child', 'website' => 'http://drupal.org/node/517688', 'version' => '1.0', 'js' => array( $module_path . '/overlay-child.js' => array(), ), 'dependencies' => array( array('system', 'ui'), ), ); return $libraries; } /** * Implements hook_form_alter(). * * For forms displayed in the overlay, add a hidden form field that lets us pass * the parent window's URL into the form. */ function overlay_form_alter(&$form, &$form_state, $form_id) { if (overlay_get_mode() == 'child') { $form['overlay_parent_url'] = array( '#type' => 'hidden', ); } } /** * 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 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 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_builder_handle_input_element() * @see _form_builder_ie_cleanup() * @see form_execute_handlers() * @see form_builder() * @see overlay_form_submit() * * @ingroup forms */ function overlay_form_after_build($form, &$form_state) { if (isset($_GET['render']) && $_GET['render'] == 'overlay') { // Form API may have already captured submit handlers from the submitted // button before after_build callback is invoked. This may have been done // by _form_builder_handle_input_element(). If so, the list of submit // handlers is stored in the $form_state array, which is something we can // also alter from here, luckily. Rememeber: our goal here is to set // $form_state['redirect'] to FALSE if the API function // overlay_request_dialog_close() has been invoked. That's because we want // to tell the parent window to close the overlay. if (!empty($form_state['submit_handlers']) && !in_array('overlay_form_submit', $form_state['submit_handlers'])) { $form_state['submit_handlers'][] = 'overlay_form_submit'; } // 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); } // If the redirect destination is the same as the parent window, just // close the overlay without redirecting the parent. if (url($form['overlay_parent_url']['#value']) == $settings['overlayChild']['redirect']) { unset($settings['overlayChild']['redirect']); } 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'); // 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('', 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 (empty($redirect)) { $path = $_GET['q']; } $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); }