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

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

neclimdul's avatar
neclimdul committed
8
use Symfony\Component\HttpFoundation\Response;
9
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
neclimdul's avatar
neclimdul committed
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
/**
neclimdul's avatar
neclimdul committed
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
  if (user_access('access overlay', $account)) {
90
    $account_data = drupal_container()->get('user.data')->get('overlay', $account->id(), 'enabled');
91
    $form['overlay_control'] = array(
92
      '#type' => 'details',
93
94
95
96
97
98
99
      '#title' => t('Administrative overlay'),
      '#weight' => 4,
    );
    $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.'),
100
      '#default_value' => isset($account_data) ? $account_data : 1,
101
    );
102
103
104
105
  }
}

/**
106
 * Implements hook_user_update().
107
 */
108
function overlay_user_update($account) {
109
  if (isset($account->overlay)) {
110
    drupal_container()->get('user.data')->set('overlay', $account->id(), 'enabled', (int) $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
  $user_data = drupal_container()->get('user.data')->get('overlay', $user->uid, 'enabled');
  $use_overlay = !isset($user_data) || $user_data;
131
  if (empty($mode) && user_access('access overlay') && $use_overlay) {
132
    $current_path = current_path();
133
134
135
136
    // 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']);
137
      drupal_goto('<front>', array('fragment' => 'overlay=' . $current_path));
138
139
    }

140
    if (isset($_GET['render']) && $_GET['render'] == 'overlay') {
141
142
143
144
145
146
147
148
149
      // 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)) {
150
        overlay_close_dialog($current_path, array('query' => drupal_get_query_parameters(NULL, array('render'))));
151
      }
152

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

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

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

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

  return $libraries;
}

/**
 * Implements hook_drupal_goto_alter().
 */
function overlay_drupal_goto_alter(&$path, &$options, &$http_response_code) {
  if (overlay_get_mode() == 'child') {
247
248
249
250
251
252
253
254
255
256
257
258
259
260
    // 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.
261
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
    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;
    }
  }
301

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

312
/**
313
314
315
316
317
 * 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.
318
319
320
321
322
323
324
325
326
 *
 * @see overlay_user_dismiss_message()
 * @see overlay_menu()
 */
function overlay_user_dismiss_message_access() {
  global $user;
  if (!user_access('access overlay')) {
    return FALSE;
  }
327
328
  // 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
329
330
331
  // the overlay, so there is nothing to dismiss.
  if (empty($user->uid)) {
    return FALSE;
332
  }
333
  return TRUE;
334
335
}

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

  // @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.
351
  $token = drupal_container()->get('request')->query->get('token');
352
  if (!isset($token) || !drupal_valid_token($token, 'overlay')) {
353
    throw new AccessDeniedHttpException();
354
355
  }

356
  drupal_container()->get('user.data')->set('overlay', $user->uid, 'message_dismissed', 1);
357
358
359
  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');
360
361
362
363
364
365
366
367
}

/**
 * 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
368
369
370
371
372
373
 * 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
374
375
376
377
 */
function overlay_disable_message() {
  global $user;

378
379
380
381
382
383
384
  $build = array();
  if (empty($user->uid) || !user_access('access overlay')) {
    return $build;
  }

  $user_data = drupal_container()->get('user.data')->get('overlay', $user->uid);
  if (empty($user_data['message_dismissed']) && (!isset($user_data['enabled']) || $user_data['enabled'])) {
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
    $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(),
          ),
        ),
      )
    );
  }

  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_access().
468
 */
469
function overlay_block_access($block) {
470
471
472
473
474
475
476
  // 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()) {
477
    if (!in_array($block->get('region'), $regions_to_render)) {
478
      return FALSE;
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
    }
  }
}

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

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

510
/**
511
 * Implements hook_preprocess_HOOK() for maintenance-page.tpl.php.
512
513
514
515
516
517
518
519
 *
 * 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);
}

520
/**
521
522
523
524
 * 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.
525
 *
526
 * @see template_process_overlay()
527
528
529
 * @see overlay.tpl.php
 */
function template_preprocess_overlay(&$variables) {
530
531
532
  $variables['tabs'] = menu_primary_local_tasks();
  $variables['title'] = drupal_get_title();
  $variables['disable_overlay'] = overlay_disable_message();
533
  $variables['content_attributes']['class'][] = 'clearfix';
534
535
536
}

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

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

561
/**
562
 * Stores and returns whether an empty page override is needed.
563
 *
564
565
566
567
568
569
570
571
572
573
 * 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).
574
575
576
577
578
579
580
581
 *
 * @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.
582
583
 *
 * @see overlay_page_delivery_callback_alter()
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
 */
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';
  }
}

/**
603
 * Prints an empty page.
604
605
606
607
608
609
610
611
612
613
 *
 * 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();
}

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

/**
624
 * Sets the overlay mode and adds proper JavaScript and styles to the page.
625
 *
626
627
628
629
 * 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.
630
 *
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
 * @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.
646
647
648
649
650
651
652
 *   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
653
 * @see overlay_init()
654
655
656
657
658
659
660
661
662
663
664
665
666
667
 */
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':
668
      drupal_add_library('overlay', 'drupal.overlay.parent');
669
670
671
672
673
674

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

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

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

/**
739
 * Requests that the overlay closes when the page is displayed.
740
741
 *
 * @param $redirect
742
743
744
 *   (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.
745
 *
746
747
748
 * @param $redirect_options
 *   (optional) An associative array of options to use when generating the
 *   redirect URL.
749
 */
750
function overlay_close_dialog($redirect = NULL, $redirect_options = array()) {
751
752
753
754
755
  $settings = array(
    'overlayChild' => array(
      'closeOverlay' => TRUE,
    ),
  );
756
757
758
759
760
761

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

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

  // 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);
767
768
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
}

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

/**
809
 * Returns a list of page regions related to the overlay.
810
811
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
 *
 * @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()
842
 * @see overlay_block_access()
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
 * @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()
864
 * @see overlay_block_access()
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
 * @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
882
883
884
 * 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.
885
886
887
888
889
890
891
892
893
 *
 * @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
894
  // be hidden by overlay_page_alter() and overlay_block_access().
895
896
897
898
899
900
901
902
903
904
905
906
907
  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(),
  );
908
909
910
911
912
913
  // 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();
914
  $original_libraries = drupal_static('drupal_add_library');
915
916
  $js = &drupal_static('drupal_add_js');
  $css = &drupal_static('drupal_add_css');
917
  $libraries = &drupal_static('drupal_add_library');
918
  $markup = drupal_render_page($page);
919
920
  $js = $original_js;
  $css = $original_css;
921
  $libraries = $original_libraries;
922
923
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
  // 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;
}

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

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

/**
999
 * Checks if the parent window needs to be refreshed on this page load.
1000
 *