overlay.module 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024
  1. <?php
  2. /**
  3. * @file
  4. * Displays the Drupal administration interface in an overlay.
  5. */
  6. /**
  7. * Implements hook_help().
  8. */
  9. function overlay_help($path, $arg) {
  10. switch ($path) {
  11. case 'admin/help#overlay':
  12. $output = '';
  13. $output .= '<h3>' . t('About') . '</h3>';
  14. $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/documentation/modules/overlay')) . '</p>';
  15. return $output;
  16. }
  17. }
  18. /**
  19. * Implements hook_menu().
  20. */
  21. function overlay_menu() {
  22. $items['overlay-ajax/%'] = array(
  23. 'title' => '',
  24. 'page callback' => 'overlay_ajax_render_region',
  25. 'page arguments' => array(1),
  26. 'access arguments' => array('access overlay'),
  27. 'type' => MENU_CALLBACK,
  28. );
  29. $items['overlay/dismiss-message'] = array(
  30. 'title' => '',
  31. 'page callback' => 'overlay_user_dismiss_message',
  32. 'access arguments' => array('access overlay'),
  33. 'type' => MENU_CALLBACK,
  34. );
  35. return $items;
  36. }
  37. /**
  38. * Implements hook_admin_paths().
  39. */
  40. function overlay_admin_paths() {
  41. $paths = array(
  42. // This is marked as an administrative path so that if it is visited from
  43. // within the overlay, the user will stay within the overlay while the
  44. // callback is being processed.
  45. 'overlay/dismiss-message' => TRUE,
  46. );
  47. return $paths;
  48. }
  49. /**
  50. * Implements hook_permission().
  51. */
  52. function overlay_permission() {
  53. return array(
  54. 'access overlay' => array(
  55. 'title' => t('Access the administrative overlay'),
  56. 'description' => t('View administrative pages in the overlay.'),
  57. ),
  58. );
  59. }
  60. /**
  61. * Implements hook_theme().
  62. */
  63. function overlay_theme() {
  64. return array(
  65. 'overlay' => array(
  66. 'render element' => 'page',
  67. 'template' => 'overlay',
  68. ),
  69. 'overlay_disable_message' => array(
  70. 'render element' => 'element',
  71. ),
  72. );
  73. }
  74. /**
  75. * Implements hook_form_alter().
  76. */
  77. function overlay_form_alter(&$form, &$form_state) {
  78. // Add a hidden element to prevent dropping out of the overlay when a form is
  79. // submitted inside the overlay using a GET method.
  80. if (isset($form['#method']) && $form['#method'] == 'get' && isset($_REQUEST['render']) && $_REQUEST['render'] == 'overlay' && !isset($form['render'])) {
  81. $form['render'] = array(
  82. '#type' => 'hidden',
  83. '#value' => 'overlay',
  84. );
  85. }
  86. }
  87. /**
  88. * Implements hook_form_FORM_ID_alter().
  89. */
  90. function overlay_form_user_profile_form_alter(&$form, &$form_state) {
  91. if ($form['#user_category'] == 'account') {
  92. $account = $form['#user'];
  93. if (user_access('access overlay', $account)) {
  94. $form['overlay_control'] = array(
  95. '#type' => 'fieldset',
  96. '#title' => t('Administrative overlay'),
  97. '#weight' => 4,
  98. '#collapsible' => TRUE,
  99. );
  100. $form['overlay_control']['overlay'] = array(
  101. '#type' => 'checkbox',
  102. '#title' => t('Use the overlay for administrative pages.'),
  103. '#description' => t('Show administrative pages on top of the page you started from.'),
  104. '#default_value' => isset($account->data['overlay']) ? $account->data['overlay'] : 1,
  105. );
  106. }
  107. }
  108. }
  109. /**
  110. * Implements hook_user_presave().
  111. */
  112. function overlay_user_presave(&$edit, $account, $category) {
  113. if (isset($edit['overlay'])) {
  114. $edit['data']['overlay'] = $edit['overlay'];
  115. }
  116. }
  117. /**
  118. * Implements hook_init().
  119. *
  120. * Determine whether the current page request is destined to appear in the
  121. * parent window or in the overlay window, and format the page accordingly.
  122. *
  123. * @see overlay_set_mode()
  124. */
  125. function overlay_init() {
  126. global $user;
  127. $mode = overlay_get_mode();
  128. // Only act if the user has access to the overlay and a mode was not already
  129. // set. Other modules can also enable the overlay directly for other uses.
  130. $use_overlay = !isset($user->data['overlay']) || $user->data['overlay'];
  131. if (empty($mode) && user_access('access overlay') && $use_overlay) {
  132. $current_path = current_path();
  133. // After overlay is enabled on the modules page, redirect to
  134. // <front>#overlay=admin/modules to actually enable the overlay.
  135. if (isset($_SESSION['overlay_enable_redirect']) && $_SESSION['overlay_enable_redirect']) {
  136. unset($_SESSION['overlay_enable_redirect']);
  137. drupal_goto('<front>', array('fragment' => 'overlay=' . $current_path));
  138. }
  139. if (isset($_GET['render']) && $_GET['render'] == 'overlay') {
  140. // If a previous page requested that we close the overlay, close it and
  141. // redirect to the final destination.
  142. if (isset($_SESSION['overlay_close_dialog'])) {
  143. call_user_func_array('overlay_close_dialog', $_SESSION['overlay_close_dialog']);
  144. unset($_SESSION['overlay_close_dialog']);
  145. }
  146. // If this page shouldn't be rendered inside the overlay, redirect to the
  147. // parent.
  148. elseif (!path_is_admin($current_path)) {
  149. // Prevent open redirects by ensuring the current path is not an absolute URL.
  150. if (url_is_external($current_path)) {
  151. $current_path = '<front>';
  152. }
  153. overlay_close_dialog($current_path, array('query' => drupal_get_query_parameters(NULL, array('q', 'render'))));
  154. }
  155. // Indicate that we are viewing an overlay child page.
  156. overlay_set_mode('child');
  157. // Unset the render parameter to avoid it being included in URLs on the page.
  158. unset($_GET['render']);
  159. }
  160. // Do not enable the overlay if we already are on an admin page.
  161. elseif (!path_is_admin($current_path)) {
  162. // Otherwise add overlay parent code and our behavior.
  163. overlay_set_mode('parent');
  164. }
  165. }
  166. }
  167. /**
  168. * Implements hook_exit().
  169. *
  170. * When viewing an overlay child page, check if we need to trigger a refresh of
  171. * the supplemental regions of the overlay on the next page request.
  172. */
  173. function overlay_exit() {
  174. // Check that we are in an overlay child page. Note that this should never
  175. // return TRUE on a cached page view, since the child mode is not set until
  176. // overlay_init() is called.
  177. if (overlay_get_mode() == 'child') {
  178. // Load any markup that was stored earlier in the page request, via calls
  179. // to overlay_store_rendered_content(). If none was stored, this is not a
  180. // page request where we expect any changes to the overlay supplemental
  181. // regions to have occurred, so we do not need to proceed any further.
  182. $original_markup = overlay_get_rendered_content();
  183. if (!empty($original_markup)) {
  184. // Compare the original markup to the current markup that we get from
  185. // rendering each overlay supplemental region now. If they don't match,
  186. // something must have changed, so we request a refresh of that region
  187. // within the parent window on the next page request.
  188. foreach (overlay_supplemental_regions() as $region) {
  189. if (!isset($original_markup[$region]) || $original_markup[$region] != overlay_render_region($region)) {
  190. overlay_request_refresh($region);
  191. }
  192. }
  193. }
  194. }
  195. }
  196. /**
  197. * Implements hook_library().
  198. */
  199. function overlay_library() {
  200. $module_path = drupal_get_path('module', 'overlay');
  201. // Overlay parent.
  202. $libraries['parent'] = array(
  203. 'title' => 'Overlay: Parent',
  204. 'website' => 'http://drupal.org/documentation/modules/overlay',
  205. 'version' => '1.0',
  206. 'js' => array(
  207. $module_path . '/overlay-parent.js' => array(),
  208. ),
  209. 'css' => array(
  210. $module_path . '/overlay-parent.css' => array(),
  211. ),
  212. 'dependencies' => array(
  213. array('system', 'ui'),
  214. array('system', 'jquery.bbq'),
  215. ),
  216. );
  217. // Overlay child.
  218. $libraries['child'] = array(
  219. 'title' => 'Overlay: Child',
  220. 'website' => 'http://drupal.org/documentation/modules/overlay',
  221. 'version' => '1.0',
  222. 'js' => array(
  223. $module_path . '/overlay-child.js' => array(),
  224. ),
  225. 'css' => array(
  226. $module_path . '/overlay-child.css' => array(),
  227. ),
  228. );
  229. return $libraries;
  230. }
  231. /**
  232. * Implements hook_drupal_goto_alter().
  233. */
  234. function overlay_drupal_goto_alter(&$path, &$options, &$http_response_code) {
  235. if (overlay_get_mode() == 'child') {
  236. // The authorize.php script bootstraps Drupal to a very low level, where
  237. // the PHP code that is necessary to close the overlay properly will not be
  238. // loaded. Therefore, if we are redirecting to authorize.php inside the
  239. // overlay, instead redirect back to the current page with instructions to
  240. // close the overlay there before redirecting to the final destination; see
  241. // overlay_init().
  242. if ($path == system_authorized_get_url() || $path == system_authorized_batch_processing_url()) {
  243. $_SESSION['overlay_close_dialog'] = array($path, $options);
  244. $path = current_path();
  245. $options = drupal_get_query_parameters();
  246. }
  247. // If the current page request is inside the overlay, add ?render=overlay
  248. // to the new path, so that it appears correctly inside the overlay.
  249. if (isset($options['query'])) {
  250. $options['query'] += array('render' => 'overlay');
  251. }
  252. else {
  253. $options['query'] = array('render' => 'overlay');
  254. }
  255. }
  256. }
  257. /**
  258. * Implements hook_batch_alter().
  259. *
  260. * If the current page request is inside the overlay, add ?render=overlay to
  261. * the success callback URL, so that it appears correctly within the overlay.
  262. *
  263. * @see overlay_get_mode()
  264. */
  265. function overlay_batch_alter(&$batch) {
  266. if (overlay_get_mode() == 'child') {
  267. if (isset($batch['url_options']['query'])) {
  268. $batch['url_options']['query']['render'] = 'overlay';
  269. }
  270. else {
  271. $batch['url_options']['query'] = array('render' => 'overlay');
  272. }
  273. }
  274. }
  275. /**
  276. * Implements hook_page_alter().
  277. */
  278. function overlay_page_alter(&$page) {
  279. // If we are limiting rendering to a subset of page regions, deny access to
  280. // all other regions so that they will not be processed.
  281. if ($regions_to_render = overlay_get_regions_to_render()) {
  282. $skipped_regions = array_diff(element_children($page), $regions_to_render);
  283. foreach ($skipped_regions as $skipped_region) {
  284. $page[$skipped_region]['#access'] = FALSE;
  285. }
  286. }
  287. $mode = overlay_get_mode();
  288. if ($mode == 'child') {
  289. // Add the overlay wrapper before the html wrapper.
  290. array_unshift($page['#theme_wrappers'], 'overlay');
  291. }
  292. elseif ($mode == 'parent' && ($message = overlay_disable_message())) {
  293. $page['page_top']['disable_overlay'] = $message;
  294. }
  295. }
  296. /**
  297. * Page callback: Dismisses the overlay accessibility message for this user.
  298. *
  299. * @return
  300. * A render array for a page containing a list of content.
  301. */
  302. function overlay_user_dismiss_message() {
  303. global $user;
  304. // It's unlikely, but possible that "access overlay" permission is granted to
  305. // the anonymous role. In this case, we do not display the message to disable
  306. // the overlay, so there is nothing to dismiss. Also, protect against
  307. // cross-site request forgeries by validating a token.
  308. if (empty($user->uid) || !isset($_GET['token']) || !drupal_valid_token($_GET['token'], 'overlay')) {
  309. return MENU_ACCESS_DENIED;
  310. }
  311. else {
  312. user_save(user_load($user->uid), array('data' => array('overlay_message_dismissed' => 1)));
  313. drupal_set_message(t('The message has been dismissed. You can change your overlay settings at any time by visiting your profile page.'));
  314. // Destination is normally given. Go to the user profile as a fallback.
  315. drupal_goto('user/' . $user->uid . '/edit');
  316. }
  317. }
  318. /**
  319. * Returns a renderable array representing a message for disabling the overlay.
  320. *
  321. * If the current user can access the overlay and has not previously indicated
  322. * that this message should be dismissed, this function returns a message
  323. * containing a link to disable the overlay. Nothing is returned for anonymous
  324. * users, because the links control per-user settings. Because some screen
  325. * readers are unable to properly read overlay contents, site builders are
  326. * discouraged from granting the "access overlay" permission to the anonymous
  327. * role.
  328. *
  329. * @see http://drupal.org/node/890284
  330. */
  331. function overlay_disable_message() {
  332. global $user;
  333. if (!empty($user->uid) && empty($user->data['overlay_message_dismissed']) && (!isset($user->data['overlay']) || $user->data['overlay']) && user_access('access overlay')) {
  334. $build = array(
  335. '#theme' => 'overlay_disable_message',
  336. '#weight' => -99,
  337. // Link to the user's profile page, where the overlay can be disabled.
  338. 'profile_link' => array(
  339. '#type' => 'link',
  340. '#title' => t('If you have problems accessing administrative pages on this site, disable the overlay on your profile page.'),
  341. '#href' => 'user/' . $user->uid . '/edit',
  342. '#options' => array(
  343. 'query' => drupal_get_destination(),
  344. 'fragment' => 'edit-overlay-control',
  345. 'attributes' => array(
  346. 'id' => 'overlay-profile-link',
  347. // Prevent the target page from being opened in the overlay.
  348. 'class' => array('overlay-exclude'),
  349. ),
  350. ),
  351. ),
  352. // Link to a menu callback that allows this message to be permanently
  353. // dismissed for the current user.
  354. 'dismiss_message_link' => array(
  355. '#type' => 'link',
  356. '#title' => t('Dismiss this message.'),
  357. '#href' => 'overlay/dismiss-message',
  358. '#options' => array(
  359. 'query' => drupal_get_destination() + array(
  360. // Add a token to protect against cross-site request forgeries.
  361. 'token' => drupal_get_token('overlay'),
  362. ),
  363. 'attributes' => array(
  364. 'id' => 'overlay-dismiss-message',
  365. // If this message is being displayed outside the overlay, prevent
  366. // this link from opening the overlay.
  367. 'class' => (overlay_get_mode() == 'parent') ? array('overlay-exclude') : array(),
  368. ),
  369. ),
  370. )
  371. );
  372. }
  373. else {
  374. $build = array();
  375. }
  376. return $build;
  377. }
  378. /**
  379. * Returns the HTML for the message about how to disable the overlay.
  380. *
  381. * @param $variables
  382. * An associative array with an 'element' element, which itself is an
  383. * associative array containing:
  384. * - profile_link: The link to this user's account.
  385. * - dismiss_message_link: The link to dismiss the overlay.
  386. *
  387. * @ingroup themeable
  388. */
  389. function theme_overlay_disable_message($variables) {
  390. $element = $variables['element'];
  391. // Add CSS classes to hide the links from most sighted users, while keeping
  392. // them accessible to screen-reader users and keyboard-only users. To assist
  393. // screen-reader users, this message appears in both the parent and child
  394. // documents, but only the one in the child document is part of the tab order.
  395. foreach (array('profile_link', 'dismiss_message_link') as $key) {
  396. $element[$key]['#options']['attributes']['class'][] = 'element-invisible';
  397. if (overlay_get_mode() == 'child') {
  398. $element[$key]['#options']['attributes']['class'][] = 'element-focusable';
  399. }
  400. }
  401. // Render the links.
  402. $output = drupal_render($element['profile_link']) . ' ' . drupal_render($element['dismiss_message_link']);
  403. // Add a heading for screen-reader users. The heading doesn't need to be seen
  404. // by sighted users.
  405. $output = '<h3 class="element-invisible">' . t('Options for the administrative overlay') . '</h3>' . $output;
  406. // Wrap in a container for styling.
  407. $output = '<div id="overlay-disable-message" class="clearfix">' . $output . '</div>';
  408. return $output;
  409. }
  410. /**
  411. * Implements hook_block_list_alter().
  412. */
  413. function overlay_block_list_alter(&$blocks) {
  414. // If we are limiting rendering to a subset of page regions, hide all blocks
  415. // which appear in regions not on that list. Note that overlay_page_alter()
  416. // does a more comprehensive job of preventing unwanted regions from being
  417. // displayed (regardless of whether they contain blocks or not), but the
  418. // reason for duplicating effort here is performance; we do not even want
  419. // these blocks to be built if they are not going to be displayed.
  420. if ($regions_to_render = overlay_get_regions_to_render()) {
  421. foreach ($blocks as $bid => $block) {
  422. if (!in_array($block->region, $regions_to_render)) {
  423. unset($blocks[$bid]);
  424. }
  425. }
  426. }
  427. }
  428. /**
  429. * Implements hook_system_info_alter().
  430. *
  431. * Add default regions for the overlay.
  432. */
  433. function overlay_system_info_alter(&$info, $file, $type) {
  434. if ($type == 'theme') {
  435. $info['overlay_regions'][] = 'content';
  436. $info['overlay_regions'][] = 'help';
  437. }
  438. }
  439. /**
  440. * Implements hook_preprocess_html().
  441. *
  442. * If the current page request is inside the overlay, add appropriate classes
  443. * to the <body> element, and simplify the page title.
  444. *
  445. * @see overlay_get_mode()
  446. */
  447. function overlay_preprocess_html(&$variables) {
  448. if (overlay_get_mode() == 'child') {
  449. // Add overlay class, so themes can react to being displayed in the overlay.
  450. $variables['classes_array'][] = 'overlay';
  451. }
  452. }
  453. /**
  454. * Implements hook_preprocess_maintenance_page().
  455. *
  456. * If the current page request is inside the overlay, add appropriate classes
  457. * to the <body> element, and simplify the page title.
  458. *
  459. * @see overlay_preprocess_maintenance_page()
  460. */
  461. function overlay_preprocess_maintenance_page(&$variables) {
  462. overlay_preprocess_html($variables);
  463. }
  464. /**
  465. * Implements template_preprocess_HOOK() for overlay.tpl.php
  466. *
  467. * If the current page request is inside the overlay, add appropriate classes
  468. * to the <body> element, and simplify the page title.
  469. *
  470. * @see template_process_overlay()
  471. * @see overlay.tpl.php
  472. */
  473. function template_preprocess_overlay(&$variables) {
  474. $variables['tabs'] = menu_primary_local_tasks();
  475. $variables['title'] = drupal_get_title();
  476. $variables['disable_overlay'] = overlay_disable_message();
  477. $variables['content_attributes_array']['class'][] = 'clearfix';
  478. }
  479. /**
  480. * Implements template_process_HOOK() for overlay.tpl.php
  481. *
  482. * Places the rendered HTML for the page body into a top level variable.
  483. *
  484. * @see template_preprocess_overlay()
  485. * @see overlay.tpl.php
  486. */
  487. function template_process_overlay(&$variables) {
  488. $variables['page'] = $variables['page']['#children'];
  489. }
  490. /**
  491. * Implements hook_preprocess_page().
  492. *
  493. * If the current page request is inside the overlay, hide the tabs.
  494. *
  495. * @see overlay_get_mode()
  496. */
  497. function overlay_preprocess_page(&$variables) {
  498. if (overlay_get_mode() == 'child') {
  499. unset($variables['tabs']['#primary']);
  500. }
  501. }
  502. /**
  503. * Stores and returns whether an empty page override is needed.
  504. *
  505. * This is used to prevent a page request which closes the overlay (for
  506. * example, a form submission) from being fully re-rendered before the overlay
  507. * is closed. Instead, we store a variable which will cause the page to be
  508. * rendered by a delivery callback function that does not actually print
  509. * visible HTML (but rather only the bare minimum scripts and styles necessary
  510. * to trigger the overlay to close), thereby allowing the dialog to be closed
  511. * faster and with less interruption, and also allowing the display of messages
  512. * to be deferred to the parent window (rather than displaying them in the
  513. * child window, which will close before the user has had a chance to read
  514. * them).
  515. *
  516. * @param $value
  517. * By default, an empty page will not be displayed. Set to TRUE to request
  518. * an empty page display, or FALSE to disable the empty page display (if it
  519. * was previously enabled on this page request).
  520. *
  521. * @return
  522. * TRUE if the current behavior is to display an empty page, or FALSE if not.
  523. *
  524. * @see overlay_page_delivery_callback_alter()
  525. */
  526. function overlay_display_empty_page($value = NULL) {
  527. $display_empty_page = &drupal_static(__FUNCTION__, FALSE);
  528. if (isset($value)) {
  529. $display_empty_page = $value;
  530. }
  531. return $display_empty_page;
  532. }
  533. /**
  534. * Implements hook_page_delivery_callback_alter().
  535. */
  536. function overlay_page_delivery_callback_alter(&$callback) {
  537. if (overlay_display_empty_page()) {
  538. $callback = 'overlay_deliver_empty_page';
  539. }
  540. }
  541. /**
  542. * Prints an empty page.
  543. *
  544. * This function is used to print out a bare minimum empty page which still has
  545. * the scripts and styles necessary in order to trigger the overlay to close.
  546. */
  547. function overlay_deliver_empty_page() {
  548. $empty_page = '<html><head><title></title>' . drupal_get_css() . drupal_get_js() . '</head><body class="overlay"></body></html>';
  549. print $empty_page;
  550. drupal_exit();
  551. }
  552. /**
  553. * Gets the current overlay mode.
  554. *
  555. * @see overlay_set_mode()
  556. */
  557. function overlay_get_mode() {
  558. return overlay_set_mode(NULL);
  559. }
  560. /**
  561. * Sets the overlay mode and adds proper JavaScript and styles to the page.
  562. *
  563. * Note that since setting the overlay mode triggers a variety of behaviors
  564. * (including hooks being invoked), it can only be done once per page request.
  565. * Therefore, the first call to this function which passes along a value of the
  566. * $mode parameter controls the overlay mode that will be used.
  567. *
  568. * @param $mode
  569. * To set the mode, pass in one of the following values:
  570. * - 'parent': This is used in the context of a parent window (a regular
  571. * browser window). If set, JavaScript is added so that administrative
  572. * links in the parent window will open in an overlay.
  573. * - 'child': This is used in the context of the child overlay window (the
  574. * page actually appearing within the overlay iframe). If set, JavaScript
  575. * and CSS are added so that Drupal behaves nicely from within the overlay.
  576. * - 'none': This is used to avoid adding any overlay-related code to the
  577. * page at all. Modules can set this to explicitly prevent the overlay from
  578. * being used. For example, since the overlay module itself sets the mode
  579. * to 'parent' or 'child' in overlay_init() when certain conditions are
  580. * met, other modules which want to override that behavior can do so by
  581. * setting the mode to 'none' earlier in the page request - e.g., in their
  582. * own hook_init() implementations, if they have a lower weight.
  583. * This parameter is optional, and if omitted, the current mode will be
  584. * returned with no action taken.
  585. *
  586. * @return
  587. * The current mode, if any has been set, or NULL if no mode has been set.
  588. *
  589. * @ingroup overlay_api
  590. * @see overlay_init()
  591. */
  592. function overlay_set_mode($mode = NULL) {
  593. global $base_path;
  594. $overlay_mode = &drupal_static(__FUNCTION__);
  595. // Make sure external resources are not included more than once. Also return
  596. // the current mode, if no mode was specified.
  597. if (isset($overlay_mode) || !isset($mode)) {
  598. return $overlay_mode;
  599. }
  600. $overlay_mode = $mode;
  601. switch ($overlay_mode) {
  602. case 'parent':
  603. drupal_add_library('overlay', 'parent');
  604. // Allow modules to act upon overlay events.
  605. module_invoke_all('overlay_parent_initialize');
  606. break;
  607. case 'child':
  608. drupal_add_library('overlay', 'child');
  609. // Allow modules to act upon overlay events.
  610. module_invoke_all('overlay_child_initialize');
  611. break;
  612. }
  613. return $overlay_mode;
  614. }
  615. /**
  616. * Implements hook_overlay_parent_initialize().
  617. */
  618. function overlay_overlay_parent_initialize() {
  619. // Let the client side know which paths are administrative.
  620. $paths = path_get_admin_paths();
  621. foreach ($paths as &$type) {
  622. $type = str_replace('<front>', variable_get('site_frontpage', 'node'), $type);
  623. }
  624. drupal_add_js(array('overlay' => array('paths' => $paths)), 'setting');
  625. $path_prefixes = array();
  626. if (module_exists('locale') && variable_get('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX) == LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX) {
  627. // Get languages grouped by status and select only the enabled ones.
  628. $languages = language_list('enabled');
  629. $languages = $languages[1];
  630. $path_prefixes = array();
  631. foreach ($languages as $language) {
  632. if ($language->prefix) {
  633. $path_prefixes[] = $language->prefix;
  634. }
  635. }
  636. }
  637. drupal_add_js(array('overlay' => array('pathPrefixes' => $path_prefixes)), 'setting');
  638. // Pass along the Ajax callback for rerendering sections of the parent window.
  639. drupal_add_js(array('overlay' => array('ajaxCallback' => 'overlay-ajax')), 'setting');
  640. }
  641. /**
  642. * Implements hook_overlay_child_initialize().
  643. */
  644. function overlay_overlay_child_initialize() {
  645. // Check if the parent window needs to refresh any page regions on this page
  646. // request.
  647. overlay_trigger_refresh();
  648. // If this is a POST request, or a GET request with a token parameter, we
  649. // have an indication that something in the supplemental regions of the
  650. // overlay might change during the current page request. We therefore store
  651. // the initial rendered content of those regions here, so that we can compare
  652. // it to the same content rendered in overlay_exit(), at the end of the page
  653. // request. This allows us to check if anything actually did change, and, if
  654. // so, trigger an immediate Ajax refresh of the parent window.
  655. if (!empty($_POST) || isset($_GET['token'])) {
  656. foreach (overlay_supplemental_regions() as $region) {
  657. overlay_store_rendered_content($region, overlay_render_region($region));
  658. }
  659. // In addition, notify the parent window that when the overlay closes,
  660. // the entire parent window should be refreshed.
  661. overlay_request_page_refresh();
  662. }
  663. // Indicate that when the main page rendering occurs later in the page
  664. // request, only the regions that appear within the overlay should be
  665. // rendered.
  666. overlay_set_regions_to_render(overlay_regions());
  667. }
  668. /**
  669. * Requests that the overlay closes when the page is displayed.
  670. *
  671. * @param $redirect
  672. * (optional) The path that should open in the parent window after the
  673. * overlay closes. If not set, no redirect will be performed on the parent
  674. * window.
  675. *
  676. * @param $redirect_options
  677. * (optional) An associative array of options to use when generating the
  678. * redirect URL.
  679. */
  680. function overlay_close_dialog($redirect = NULL, $redirect_options = array()) {
  681. $settings = array(
  682. 'overlayChild' => array(
  683. 'closeOverlay' => TRUE,
  684. ),
  685. );
  686. // Tell the child window to perform the redirection when requested to.
  687. if (isset($redirect)) {
  688. $settings['overlayChild']['redirect'] = url($redirect, $redirect_options);
  689. }
  690. drupal_add_js($settings, array('type' => 'setting'));
  691. // Since we are closing the overlay as soon as the page is displayed, we do
  692. // not want to show any of the page's actual content.
  693. overlay_display_empty_page(TRUE);
  694. }
  695. /**
  696. * Returns a list of page regions that appear in the overlay.
  697. *
  698. * Overlay regions correspond to the entire contents of the overlay child
  699. * window and are refreshed each time a new page request is made within the
  700. * overlay.
  701. *
  702. * @return
  703. * An array of region names that correspond to those which appear in the
  704. * overlay, within the theme that is being used to display the current page.
  705. *
  706. * @see overlay_supplemental_regions()
  707. */
  708. function overlay_regions() {
  709. return _overlay_region_list('overlay_regions');
  710. }
  711. /**
  712. * Returns a list of supplemental page regions for the overlay.
  713. *
  714. * Supplemental overlay regions are those which are technically part of the
  715. * parent window, but appear to the user as being related to the overlay
  716. * (usually because they are displayed next to, rather than underneath, the
  717. * main overlay regions) and therefore need to be dynamically refreshed if any
  718. * administrative actions taken within the overlay change their contents.
  719. *
  720. * An example of a typical overlay supplemental region would be the 'page_top'
  721. * region, in the case where a toolbar is being displayed there.
  722. *
  723. * @return
  724. * An array of region names that correspond to supplemental overlay regions,
  725. * within the theme that is being used to display the current page.
  726. *
  727. * @see overlay_regions()
  728. */
  729. function overlay_supplemental_regions() {
  730. return _overlay_region_list('overlay_supplemental_regions');
  731. }
  732. /**
  733. * Returns a list of page regions related to the overlay.
  734. *
  735. * @param $type
  736. * The type of regions to return. This can either be 'overlay_regions' or
  737. * 'overlay_supplemental_regions'.
  738. *
  739. * @return
  740. * An array of region names of the given type, within the theme that is being
  741. * used to display the current page.
  742. *
  743. * @see overlay_regions()
  744. * @see overlay_supplemental_regions()
  745. */
  746. function _overlay_region_list($type) {
  747. // Obtain the current theme. We need to first make sure the theme system is
  748. // initialized, since this function can be called early in the page request.
  749. drupal_theme_initialize();
  750. $themes = list_themes();
  751. $theme = $themes[$GLOBALS['theme']];
  752. // Return the list of regions stored within the theme's info array, or an
  753. // empty array if no regions of the appropriate type are defined.
  754. return !empty($theme->info[$type]) ? $theme->info[$type] : array();
  755. }
  756. /**
  757. * Returns a list of page regions that rendering should be limited to.
  758. *
  759. * @return
  760. * An array containing the names of the regions that will be rendered when
  761. * drupal_render_page() is called. If empty, then no limits will be imposed,
  762. * and all regions of the page will be rendered.
  763. *
  764. * @see overlay_page_alter()
  765. * @see overlay_block_list_alter()
  766. * @see overlay_set_regions_to_render()
  767. */
  768. function overlay_get_regions_to_render() {
  769. return overlay_set_regions_to_render();
  770. }
  771. /**
  772. * Sets the regions of the page that rendering will be limited to.
  773. *
  774. * @param $regions
  775. * (Optional) An array containing the names of the regions that should be
  776. * rendered when drupal_render_page() is called. Pass in an empty array to
  777. * remove all limits and cause drupal_render_page() to render all page
  778. * regions (the default behavior). If this parameter is omitted, no change
  779. * will be made to the current list of regions to render.
  780. *
  781. * @return
  782. * The current list of regions to render, or an empty array if the regions
  783. * are not being limited.
  784. *
  785. * @see overlay_page_alter()
  786. * @see overlay_block_list_alter()
  787. * @see overlay_get_regions_to_render()
  788. */
  789. function overlay_set_regions_to_render($regions = NULL) {
  790. $regions_to_render = &drupal_static(__FUNCTION__, array());
  791. if (isset($regions)) {
  792. $regions_to_render = $regions;
  793. }
  794. return $regions_to_render;
  795. }
  796. /**
  797. * Renders an individual page region.
  798. *
  799. * This function is primarily intended to be used for checking the content of
  800. * supplemental overlay regions (e.g., a region containing a toolbar). Passing
  801. * in a region that is intended to display the main page content is not
  802. * supported; the region will be rendered by this function, but the main page
  803. * content will not appear in it. In addition, although this function returns
  804. * the rendered HTML for the provided region, it does not place it on the final
  805. * page, nor add any of its associated JavaScript or CSS to the page.
  806. *
  807. * @param $region
  808. * The name of the page region that should be rendered.
  809. *
  810. * @return
  811. * The rendered HTML of the provided region.
  812. */
  813. function overlay_render_region($region) {
  814. // Indicate the region that we will be rendering, so that other regions will
  815. // be hidden by overlay_page_alter() and overlay_block_list_alter().
  816. overlay_set_regions_to_render(array($region));
  817. // Do what is necessary to force drupal_render_page() to only display HTML
  818. // from the requested region. Specifically, declare that the main page
  819. // content does not need to automatically be added to the page, and pass in
  820. // a page array that has all theme functions removed (so that overall HTML
  821. // for the page will not be added either).
  822. $system_main_content_added = &drupal_static('system_main_content_added');
  823. $system_main_content_added = TRUE;
  824. $page = array(
  825. '#type' => 'page',
  826. '#theme' => NULL,
  827. '#theme_wrappers' => array(),
  828. );
  829. // Render the region, but do not cache any JavaScript or CSS associated with
  830. // it. This region might not be included the next time drupal_render_page()
  831. // is called, and we do not want its JavaScript or CSS to erroneously appear
  832. // on the final rendered page.
  833. $original_js = drupal_add_js();
  834. $original_css = drupal_add_css();
  835. $original_libraries = drupal_static('drupal_add_library');
  836. $js = &drupal_static('drupal_add_js');
  837. $css = &drupal_static('drupal_add_css');
  838. $libraries = &drupal_static('drupal_add_library');
  839. $markup = drupal_render_page($page);
  840. $js = $original_js;
  841. $css = $original_css;
  842. $libraries = $original_libraries;
  843. // Indicate that the main page content has not, in fact, been displayed, so
  844. // that future calls to drupal_render_page() will be able to render it
  845. // correctly.
  846. $system_main_content_added = FALSE;
  847. // Restore the original behavior of rendering all regions for the next time
  848. // drupal_render_page() is called.
  849. overlay_set_regions_to_render(array());
  850. return $markup;
  851. }
  852. /**
  853. * Returns any rendered content that was stored earlier in the page request.
  854. *
  855. * @return
  856. * An array of all rendered HTML that was stored earlier in the page request,
  857. * keyed by the identifier with which it was stored. If no content was
  858. * stored, an empty array is returned.
  859. *
  860. * @see overlay_store_rendered_content()
  861. */
  862. function overlay_get_rendered_content() {
  863. return overlay_store_rendered_content();
  864. }
  865. /**
  866. * Stores strings representing rendered HTML content.
  867. *
  868. * This function is used to keep a static cache of rendered content that can be
  869. * referred to later in the page request.
  870. *
  871. * @param $id
  872. * (Optional) An identifier for the content which is being stored, which will
  873. * be used as an array key in the returned array. If omitted, no change will
  874. * be made to the current stored data.
  875. * @param $content
  876. * (Optional) A string representing the rendered data to store. This only has
  877. * an effect if $id is also provided.
  878. *
  879. * @return
  880. * An array representing all data that is currently being stored, or an empty
  881. * array if there is none.
  882. *
  883. * @see overlay_get_rendered_content()
  884. */
  885. function overlay_store_rendered_content($id = NULL, $content = NULL) {
  886. $rendered_content = &drupal_static(__FUNCTION__, array());
  887. if (isset($id)) {
  888. $rendered_content[$id] = $content;
  889. }
  890. return $rendered_content;
  891. }
  892. /**
  893. * Requests that the parent window refreshes a particular page region.
  894. *
  895. * @param $region
  896. * The name of the page region to refresh. The parent window will trigger a
  897. * refresh of this region on the next page load.
  898. *
  899. * @see overlay_trigger_refresh()
  900. * @see Drupal.overlay.refreshRegions()
  901. */
  902. function overlay_request_refresh($region) {
  903. $class = drupal_region_class($region);
  904. $_SESSION['overlay_regions_to_refresh'][] = array($class => $region);
  905. }
  906. /**
  907. * Requests that the entire parent window is reloaded when the overlay closes.
  908. *
  909. * @see overlay_trigger_refresh()
  910. */
  911. function overlay_request_page_refresh() {
  912. $_SESSION['overlay_refresh_parent'] = TRUE;
  913. }
  914. /**
  915. * Checks if the parent window needs to be refreshed on this page load.
  916. *
  917. * If the previous page load requested that any page regions be refreshed, or
  918. * if it requested that the entire page be refreshed when the overlay closes,
  919. * pass that request via JavaScript to the child window, so it can in turn pass
  920. * the request to the parent window.
  921. *
  922. * @see overlay_request_refresh()
  923. * @see overlay_request_page_refresh()
  924. * @see Drupal.overlay.refreshRegions()
  925. */
  926. function overlay_trigger_refresh() {
  927. if (!empty($_SESSION['overlay_regions_to_refresh'])) {
  928. $settings = array(
  929. 'overlayChild' => array(
  930. 'refreshRegions' => $_SESSION['overlay_regions_to_refresh'],
  931. ),
  932. );
  933. drupal_add_js($settings, array('type' => 'setting'));
  934. unset($_SESSION['overlay_regions_to_refresh']);
  935. }
  936. if (!empty($_SESSION['overlay_refresh_parent'])) {
  937. drupal_add_js(array('overlayChild' => array('refreshPage' => TRUE)), array('type' => 'setting'));
  938. unset($_SESSION['overlay_refresh_parent']);
  939. }
  940. }
  941. /**
  942. * Prints the markup obtained by rendering a single region of the page.
  943. *
  944. * This function is intended to be called via Ajax.
  945. *
  946. * @param $region
  947. * The name of the page region to render.
  948. *
  949. * @see Drupal.overlay.refreshRegions()
  950. */
  951. function overlay_ajax_render_region($region) {
  952. print overlay_render_region($region);
  953. }