overlay.module 36.1 KB
Newer Older
1 2 3 4 5 6 7
<?php

/**
 * @file
 * Displays the Drupal administration interface in an overlay.
 */

8
use Symfony\Component\HttpFoundation\Response;
9
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
10

11 12 13 14 15 16 17 18
/**
 * Implements hook_help().
 */
function overlay_help($path, $arg) {
  switch ($path) {
    case 'admin/help#overlay':
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
19
      $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>';
20 21 22 23
      return $output;
  }
}

24
/**
25
 * Implements hook_menu()
26 27 28 29 30 31 32 33 34
 */
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,
  );
35 36 37
  $items['overlay/dismiss-message'] = array(
    'title' => '',
    'page callback' => 'overlay_user_dismiss_message',
38
    'access callback' => 'overlay_user_dismiss_message_access',
39 40
    'type' => MENU_CALLBACK,
  );
41 42 43
  return $items;
}

44 45 46 47 48 49 50 51 52 53 54 55 56
/**
 * Implements hook_admin_paths().
 */
function overlay_admin_paths() {
  $paths = array(
    // This is marked as an administrative path so that if it is visited from
    // within the overlay, the user will stay within the overlay while the
    // callback is being processed.
    'overlay/dismiss-message' => TRUE,
  );
  return $paths;
}

57 58 59 60 61 62 63 64 65 66 67 68
/**
 * 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.'),
    ),
  );
}

69 70 71 72 73 74 75 76 77
/**
 * Implements hook_theme().
 */
function overlay_theme() {
  return array(
    'overlay' => array(
      'render element' => 'page',
      'template' => 'overlay',
    ),
78 79 80
    'overlay_disable_message' => array(
      'render element' => 'element',
    ),
81 82 83
  );
}

84 85 86 87
/**
 * Implements hook_form_FORM_ID_alter().
 */
function overlay_form_user_profile_form_alter(&$form, &$form_state) {
88
  $account = $form_state['controller']->getEntity($form_state);
89 90
  if (user_access('access overlay', $account)) {
    $form['overlay_control'] = array(
91
      '#type' => 'details',
92 93 94 95 96 97 98 99 100 101
      '#title' => t('Administrative overlay'),
      '#weight' => 4,
      '#collapsible' => TRUE,
    );
    $form['overlay_control']['overlay'] = array(
      '#type' => 'checkbox',
      '#title' => t('Use the overlay for administrative pages.'),
      '#description' => t('Show administrative pages on top of the page you started from.'),
      '#default_value' => isset($account->data['overlay']) ? $account->data['overlay'] : 1,
    );
102 103 104 105 106 107
  }
}

/**
 * Implements hook_user_presave().
 */
108 109 110
function overlay_user_presave($account) {
  if (isset($account->overlay)) {
    $account->data['overlay'] = $account->overlay;
111 112 113
  }
}

114 115 116 117 118 119 120 121 122
/**
 * 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() {
123 124
  global $user;

125 126 127 128
  $mode = overlay_get_mode();

  // Only act if the user has access to the overlay and a mode was not already
  // set. Other modules can also enable the overlay directly for other uses.
129 130
  $use_overlay = !isset($user->data['overlay']) || $user->data['overlay'];
  if (empty($mode) && user_access('access overlay') && $use_overlay) {
131
    $current_path = current_path();
132 133 134 135
    // 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']);
136
      drupal_goto('<front>', array('fragment' => 'overlay=' . $current_path));
137 138
    }

139
    if (isset($_GET['render']) && $_GET['render'] == 'overlay') {
140 141 142 143 144 145 146 147 148
      // If a previous page requested that we close the overlay, close it and
      // redirect to the final destination.
      if (isset($_SESSION['overlay_close_dialog'])) {
        call_user_func_array('overlay_close_dialog', $_SESSION['overlay_close_dialog']);
        unset($_SESSION['overlay_close_dialog']);
      }
      // If this page shouldn't be rendered inside the overlay, redirect to the
      // parent.
      elseif (!path_is_admin($current_path)) {
149
        overlay_close_dialog($current_path, array('query' => drupal_get_query_parameters(NULL, array('render'))));
150
      }
151

152 153
      // Indicate that we are viewing an overlay child page.
      overlay_set_mode('child');
154 155 156

      // Unset the render parameter to avoid it being included in URLs on the page.
      unset($_GET['render']);
157
    }
158
    // Do not enable the overlay if we already are on an admin page.
159
    elseif (!path_is_admin($current_path)) {
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
      // 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);
        }
      }
    }
  }
}

/**
197
 * Implements hook_library_info().
198
 */
199
function overlay_library_info() {
200 201 202
  $module_path = drupal_get_path('module', 'overlay');

  // Overlay parent.
203
  $libraries['drupal.overlay.parent'] = array(
204
    'title' => 'Overlay: Parent',
205
    'website' => 'http://drupal.org/documentation/modules/overlay',
206 207 208 209 210 211 212 213
    'version' => '1.0',
    'js' => array(
      $module_path . '/overlay-parent.js' => array(),
    ),
    'css' => array(
      $module_path . '/overlay-parent.css' => array(),
    ),
    'dependencies' => array(
214 215
      array('system', 'jquery'),
      array('system', 'drupal'),
216
      array('system', 'drupalSettings'),
217
      array('system', 'jquery.ui.core'),
218
      array('system', 'jquery.bbq'),
219 220 221
    ),
  );
  // Overlay child.
222
  $libraries['drupal.overlay.child'] = array(
223
    'title' => 'Overlay: Child',
224
    'website' => 'http://drupal.org/documentation/modules/overlay',
225 226 227 228
    'version' => '1.0',
    'js' => array(
      $module_path . '/overlay-child.js' => array(),
    ),
229 230
    'css' => array(
      $module_path . '/overlay-child.css' => array(),
231
    ),
232 233 234
    'dependencies' => array(
      array('system', 'jquery'),
      array('system', 'drupal'),
235
      array('system', 'drupalSettings'),
236 237
      array('system', 'jquery.once'),
    ),
238 239 240 241 242 243 244 245 246 247
  );

  return $libraries;
}

/**
 * Implements hook_drupal_goto_alter().
 */
function overlay_drupal_goto_alter(&$path, &$options, &$http_response_code) {
  if (overlay_get_mode() == 'child') {
248 249 250 251 252 253 254 255 256 257 258 259 260 261
    // The authorize.php script bootstraps Drupal to a very low level, where
    // the PHP code that is necessary to close the overlay properly will not be
    // loaded. Therefore, if we are redirecting to authorize.php inside the
    // overlay, instead redirect back to the current page with instructions to
    // close the overlay there before redirecting to the final destination; see
    // overlay_init().
    if ($path == system_authorized_get_url() || $path == system_authorized_batch_processing_url()) {
      $_SESSION['overlay_close_dialog'] = array($path, $options);
      $path = current_path();
      $options = drupal_get_query_parameters();
    }

    // If the current page request is inside the overlay, add ?render=overlay
    // to the new path, so that it appears correctly inside the overlay.
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
    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;
    }
  }
302

303 304
  $mode = overlay_get_mode();
  if ($mode == 'child') {
305 306 307
    // Add the overlay wrapper before the html wrapper.
    array_unshift($page['#theme_wrappers'], 'overlay');
  }
308 309 310 311 312
  elseif ($mode == 'parent' && ($message = overlay_disable_message())) {
    $page['page_top']['disable_overlay'] = $message;
  }
}

313
/**
314 315 316 317 318
 * Access callback: Determines access to dismiss the accessibility message.
 *
 * @return
 *   TRUE if the user has permission to dismiss the accessibility message or if
 *   the user is anonymous. FALSE if otherwise.
319 320 321 322 323 324 325 326 327
 *
 * @see overlay_user_dismiss_message()
 * @see overlay_menu()
 */
function overlay_user_dismiss_message_access() {
  global $user;
  if (!user_access('access overlay')) {
    return FALSE;
  }
328 329
  // It's unlikely, but possible that "access overlay" permission is granted to
  // the anonymous role. In this case, we do not display the message to disable
330 331 332
  // the overlay, so there is nothing to dismiss.
  if (empty($user->uid)) {
    return FALSE;
333
  }
334
  return TRUE;
335 336
}

337
/**
338 339 340 341
 * Page callback: Dismisses the overlay accessibility message for this user.
 *
 * @return
 *   A render array for a page containing a list of content.
342 343 344 345 346 347
 *
 * @see overlay_user_dismiss_message_access()
 * @see overlay_menu()
 */
function overlay_user_dismiss_message() {
  global $user;
348 349 350 351

  // @todo CSRF tokens are validated in page callbacks rather than access
  //   callbacks, because access callbacks are also invoked during menu link
  //   generation. Add token support to routing: http://drupal.org/node/755584.
352
  $token = drupal_container()->get('request')->query->get('token');
353
  if (!isset($token) || !drupal_valid_token($token, 'overlay')) {
354
    throw new AccessDeniedHttpException();
355 356
  }

357 358 359 360 361 362
  $account = user_load($user->uid);
  $account->data['overlay_message_dismissed'] = 1;
  $account->save();
  drupal_set_message(t('The message has been dismissed. You can change your overlay settings at any time by visiting your profile page.'));
  // Destination is normally given. Go to the user profile as a fallback.
  drupal_goto('user/' . $user->uid . '/edit');
363 364 365 366 367 368 369 370
}

/**
 * Returns a renderable array representing a message for disabling the overlay.
 *
 * If the current user can access the overlay and has not previously indicated
 * that this message should be dismissed, this function returns a message
 * containing a link to disable the overlay. Nothing is returned for anonymous
371 372 373 374 375 376
 * users, because the links control per-user settings. Because some screen
 * readers are unable to properly read overlay contents, site builders are
 * discouraged from granting the "access overlay" permission to the anonymous
 * role.
 *
 * @see http://drupal.org/node/890284
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430
 */
function overlay_disable_message() {
  global $user;

  if (!empty($user->uid) && empty($user->data['overlay_message_dismissed']) && (!isset($user->data['overlay']) || $user->data['overlay']) && user_access('access overlay')) {
    $build = array(
      '#theme' => 'overlay_disable_message',
      '#weight' => -99,
      // Link to the user's profile page, where the overlay can be disabled.
      'profile_link' => array(
        '#type' => 'link',
        '#title' => t('If you have problems accessing administrative pages on this site, disable the overlay on your profile page.'),
        '#href' => 'user/' . $user->uid . '/edit',
        '#options' => array(
          'query' => drupal_get_destination(),
          'fragment' => 'edit-overlay-control',
          'attributes' => array(
            'id' => 'overlay-profile-link',
            // Prevent the target page from being opened in the overlay.
            'class' => array('overlay-exclude'),
          ),
        ),
      ),
      // Link to a menu callback that allows this message to be permanently
      // dismissed for the current user.
      'dismiss_message_link' => array(
        '#type' => 'link',
        '#title' => t('Dismiss this message.'),
        '#href' => 'overlay/dismiss-message',
        '#options' => array(
          'query' => drupal_get_destination() + array(
            // Add a token to protect against cross-site request forgeries.
            'token' => drupal_get_token('overlay'),
          ),
          'attributes' => array(
            'id' => 'overlay-dismiss-message',
            // If this message is being displayed outside the overlay, prevent
            // this link from opening the overlay.
            'class' => (overlay_get_mode() == 'parent') ? array('overlay-exclude') : array(),
          ),
        ),
      )
    );
  }
  else {
    $build = array();
  }

  return $build;
}

/**
 * Returns the HTML for the message about how to disable the overlay.
 *
431 432 433 434 435 436 437
 * @param $variables
 *   An associative array with an 'element' element, which itself is an
 *   associative array containing:
 *   - profile_link: The link to this user's account.
 *   - dismiss_message_link: The link to dismiss the overlay.
 *
 * @ingroup themeable
438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463
 */
function theme_overlay_disable_message($variables) {
  $element = $variables['element'];

  // Add CSS classes to hide the links from most sighted users, while keeping
  // them accessible to screen-reader users and keyboard-only users. To assist
  // screen-reader users, this message appears in both the parent and child
  // documents, but only the one in the child document is part of the tab order.
  foreach (array('profile_link', 'dismiss_message_link') as $key) {
    $element[$key]['#options']['attributes']['class'][] = 'element-invisible';
    if (overlay_get_mode() == 'child') {
      $element[$key]['#options']['attributes']['class'][] = 'element-focusable';
    }
  }

  // Render the links.
  $output = drupal_render($element['profile_link']) . ' ' . drupal_render($element['dismiss_message_link']);

  // Add a heading for screen-reader users. The heading doesn't need to be seen
  // by sighted users.
  $output = '<h3 class="element-invisible">' . t('Options for the administrative overlay') . '</h3>' . $output;

  // Wrap in a container for styling.
  $output = '<div id="overlay-disable-message" class="clearfix">' . $output . '</div>';

  return $output;
464 465 466
}

/**
467
 * Implements hook_block_list_alter().
468
 */
469
function overlay_block_list_alter(&$blocks) {
470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497
  // 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';
  }
}

/**
498
 * Implements hook_preprocess_HOOK() for html.tpl.php.
499 500 501 502 503 504 505 506 507
 *
 * 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.
508
    $variables['attributes']['class'][] = 'overlay';
509 510 511
  }
}

512
/**
513
 * Implements hook_preprocess_HOOK() for maintenance-page.tpl.php.
514 515 516 517 518 519 520 521
 *
 * If the current page request is inside the overlay, add appropriate classes
 * to the <body> element, and simplify the page title.
 */
function overlay_preprocess_maintenance_page(&$variables) {
  overlay_preprocess_html($variables);
}

522
/**
523 524 525 526
 * Implements template_preprocess_HOOK() for overlay.tpl.php
 *
 * If the current page request is inside the overlay, add appropriate classes
 * to the <body> element, and simplify the page title.
527
 *
528
 * @see template_process_overlay()
529 530 531
 * @see overlay.tpl.php
 */
function template_preprocess_overlay(&$variables) {
532 533 534
  $variables['tabs'] = menu_primary_local_tasks();
  $variables['title'] = drupal_get_title();
  $variables['disable_overlay'] = overlay_disable_message();
535
  $variables['content_attributes']['class'][] = 'clearfix';
536 537 538
}

/**
539 540 541
 * Implements template_process_HOOK() for overlay.tpl.php
 *
 * Places the rendered HTML for the page body into a top level variable.
542 543 544 545 546 547 548 549 550
 *
 * @see template_preprocess_overlay()
 * @see overlay.tpl.php
 */
function template_process_overlay(&$variables) {
  $variables['page'] = $variables['page']['#children'];
}

/**
551
 * Implements hook_preprocess_HOOK() for page.tpl.php.
552
 *
553
 * If the current page request is inside the overlay, hide the tabs.
554 555 556 557 558
 *
 * @see overlay_get_mode()
 */
function overlay_preprocess_page(&$variables) {
  if (overlay_get_mode() == 'child') {
559
    unset($variables['tabs']['#primary']);
560
  }
Dries's avatar
Dries committed
561 562
}

563
/**
564
 * Stores and returns whether an empty page override is needed.
565
 *
566 567 568 569 570 571 572 573 574 575
 * This is used to prevent a page request which closes the overlay (for
 * example, a form submission) from being fully re-rendered before the overlay
 * is closed. Instead, we store a variable which will cause the page to be
 * rendered by a delivery callback function that does not actually print
 * visible HTML (but rather only the bare minimum scripts and styles necessary
 * to trigger the overlay to close), thereby allowing the dialog to be closed
 * faster and with less interruption, and also allowing the display of messages
 * to be deferred to the parent window (rather than displaying them in the
 * child window, which will close before the user has had a chance to read
 * them).
576 577 578 579 580 581 582 583
 *
 * @param $value
 *   By default, an empty page will not be displayed. Set to TRUE to request
 *   an empty page display, or FALSE to disable the empty page display (if it
 *   was previously enabled on this page request).
 *
 * @return
 *   TRUE if the current behavior is to display an empty page, or FALSE if not.
584 585
 *
 * @see overlay_page_delivery_callback_alter()
586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604
 */
function overlay_display_empty_page($value = NULL) {
  $display_empty_page = &drupal_static(__FUNCTION__, FALSE);
  if (isset($value)) {
    $display_empty_page = $value;
  }
  return $display_empty_page;
}

/**
 * Implements hook_page_delivery_callback_alter().
 */
function overlay_page_delivery_callback_alter(&$callback) {
  if (overlay_display_empty_page()) {
    $callback = 'overlay_deliver_empty_page';
  }
}

/**
605
 * Prints an empty page.
606 607 608 609 610 611 612 613 614 615
 *
 * This function is used to print out a bare minimum empty page which still has
 * the scripts and styles necessary in order to trigger the overlay to close.
 */
function overlay_deliver_empty_page() {
  $empty_page = '<html><head><title></title>' . drupal_get_css() . drupal_get_js() . '</head><body class="overlay"></body></html>';
  print $empty_page;
  drupal_exit();
}

616
/**
617
 * Gets the current overlay mode.
618 619 620 621 622 623 624 625
 *
 * @see overlay_set_mode()
 */
function overlay_get_mode() {
  return overlay_set_mode(NULL);
}

/**
626
 * Sets the overlay mode and adds proper JavaScript and styles to the page.
627
 *
628 629 630 631
 * Note that since setting the overlay mode triggers a variety of behaviors
 * (including hooks being invoked), it can only be done once per page request.
 * Therefore, the first call to this function which passes along a value of the
 * $mode parameter controls the overlay mode that will be used.
632
 *
633 634 635 636 637 638 639 640 641 642 643 644 645 646 647
 * @param $mode
 *   To set the mode, pass in one of the following values:
 *   - 'parent': This is used in the context of a parent window (a regular
 *     browser window). If set, JavaScript is added so that administrative
 *     links in the parent window will open in an overlay.
 *   - 'child': This is used in the context of the child overlay window (the
 *     page actually appearing within the overlay iframe). If set, JavaScript
 *     and CSS are added so that Drupal behaves nicely from within the overlay.
 *   - 'none': This is used to avoid adding any overlay-related code to the
 *     page at all. Modules can set this to explicitly prevent the overlay from
 *     being used. For example, since the overlay module itself sets the mode
 *     to 'parent' or 'child' in overlay_init() when certain conditions are
 *     met, other modules which want to override that behavior can do so by
 *     setting the mode to 'none' earlier in the page request - e.g., in their
 *     own hook_init() implementations, if they have a lower weight.
648 649 650 651 652 653 654
 *   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
655
 * @see overlay_init()
656 657 658 659 660 661 662 663 664 665 666 667 668 669
 */
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':
670
      drupal_add_library('overlay', 'drupal.overlay.parent');
671 672 673 674 675 676

      // Allow modules to act upon overlay events.
      module_invoke_all('overlay_parent_initialize');
      break;

    case 'child':
677
      drupal_add_library('overlay', 'drupal.overlay.child');
678 679 680 681 682 683 684 685 686 687 688 689 690 691 692

      // 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) {
693
    $type = str_replace('<front>', config('system.site')->get('page.front'), $type);
694 695
  }
  drupal_add_js(array('overlay' => array('paths' => $paths)), 'setting');
696
  $path_prefixes = array();
697 698
  if (module_exists('language')) {
    language_negotiation_include();
699
    if (config('language.negotiation')->get('url.source') == LANGUAGE_NEGOTIATION_URL_PREFIX) {
700 701 702 703 704
      // Skip the empty string indicating the default language. We always accept
      // paths without a prefix.
      $path_prefixes = language_negotiation_url_prefixes();
      $path_prefixes = array_values(array_filter($path_prefixes));
    }
705 706
  }
  drupal_add_js(array('overlay' => array('pathPrefixes' => $path_prefixes)), 'setting');
707
  // Pass along the Ajax callback for rerendering sections of the parent window.
708 709 710 711 712 713 714 715 716
  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.
717
  overlay_trigger_refresh();
718 719 720 721 722 723
  // 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
724
  // so, trigger an immediate Ajax refresh of the parent window.
725
  $token = drupal_container()->get('request')->query->get('token');
726
  if (!empty($_POST) || isset($token)) {
727 728 729
    foreach (overlay_supplemental_regions() as $region) {
      overlay_store_rendered_content($region, overlay_render_region($region));
    }
730 731 732
    // In addition, notify the parent window that when the overlay closes,
    // the entire parent window should be refreshed.
    overlay_request_page_refresh();
733 734 735 736 737 738 739 740
  }
  // 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());
}

/**
741
 * Requests that the overlay overlay closes when the page is displayed.
742 743
 *
 * @param $redirect
744 745 746
 *   (optional) The path that should open in the parent window after the
 *   overlay closes. If not set, no redirect will be performed on the parent
 *   window.
747
 *
748 749 750
 * @param $redirect_options
 *   (optional) An associative array of options to use when generating the
 *   redirect URL.
751
 */
752
function overlay_close_dialog($redirect = NULL, $redirect_options = array()) {
753 754 755 756 757
  $settings = array(
    'overlayChild' => array(
      'closeOverlay' => TRUE,
    ),
  );
758 759 760 761 762 763

  // Tell the child window to perform the redirection when requested to.
  if (isset($redirect)) {
    $settings['overlayChild']['redirect'] = url($redirect, $redirect_options);
  }

764
  drupal_add_js($settings, array('type' => 'setting'));
765 766 767 768

  // Since we are closing the overlay as soon as the page is displayed, we do
  // not want to show any of the page's actual content.
  overlay_display_empty_page(TRUE);
769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810
}

/**
 * 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');
}

/**
811
 * Returns a list of page regions related to the overlay.
812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843
 *
 * @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()
844
 * @see overlay_block_list_alter()
845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865
 * @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()
866
 * @see overlay_block_list_alter()
867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883
 * @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
884 885 886
 * content will not appear in it. In addition, although this function returns
 * the rendered HTML for the provided region, it does not place it on the final
 * page, nor add any of its associated JavaScript or CSS to the page.
887 888 889 890 891 892 893 894 895
 *
 * @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
896
  // be hidden by overlay_page_alter() and overlay_block_list_alter().
897 898 899 900 901 902 903 904 905 906 907 908 909
  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(),
  );
910 911 912 913 914 915
  // Render the region, but do not cache any JavaScript or CSS associated with
  // it. This region might not be included the next time drupal_render_page()
  // is called, and we do not want its JavaScript or CSS to erroneously appear
  // on the final rendered page.
  $original_js = drupal_add_js();
  $original_css = drupal_add_css();
916
  $original_libraries = drupal_static('drupal_add_library');
917 918
  $js = &drupal_static('drupal_add_js');
  $css = &drupal_static('drupal_add_css');
919
  $libraries = &drupal_static('drupal_add_library');
920
  $markup = drupal_render_page($page);
921 922
  $js = $original_js;
  $css = $original_css;
923
  $libraries = $original_libraries;
924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976
  // 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;
}

/**
977
 * Requests that the parent window refreshes a particular page region.
978 979 980 981 982
 *
 * @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.
 *
983
 * @see overlay_trigger_refresh()
984 985 986 987 988 989 990 991
 * @see Drupal.overlay.refreshRegions()
 */
function overlay_request_refresh($region) {
  $class = drupal_region_class($region);
  $_SESSION['overlay_regions_to_refresh'][] = array($class => $region);
}

/**
992
 * Requests that the entire parent window is reloaded when the overlay closes.
993
 *
994 995 996 997 998 999 1000
 * @see overlay_trigger_refresh()
 */
function overlay_request_page_refresh() {
  $_SESSION['overlay_refresh_parent'] = TRUE;
}

/**
1001
 * Checks if the parent window needs to be refreshed on this page load.
1002 1003 1004 1005 1006
 *
 * If the previous page load requested that any page regions be refreshed, or
 * if it requested that the entire page be refreshed when the overlay closes,
 * pass that request via JavaScript to the child window, so it can in turn pass
 * the request to the parent window.
1007 1008
 *
 * @see overlay_request_refresh()
1009
 * @see overlay_request_page_refresh()
1010 1011
 * @see Drupal.overlay.refreshRegions()
 */
1012
function overlay_trigger_refresh() {
1013 1014 1015 1016 1017 1018 1019 1020 1021
  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']);
  }
1022 1023 1024 1025
  if (!empty($_SESSION['overlay_refresh_parent'])) {
    drupal_add_js(array('overlayChild' => array('refreshPage' => TRUE)), array('type' => 'setting'));
    unset($_SESSION['overlay_refresh_parent']);
  }
1026 1027 1028 1029 1030
}

/**
 * Prints the markup obtained by rendering a single region of the page.
 *
1031
 * This function is intended to be called via Ajax.
1032 1033 1034 1035 1036 1037 1038
 *
 * @param $region
 *   The name of the page region to render.
 *
 * @see Drupal.overlay.refreshRegions()
 */
function overlay_ajax_render_region($region) {
1039
  return new Response(overlay_render_region($region));
1040
}