core/includes/common.inc | 22 +++-- .../system/src/Tests/Common/PageRenderTest.php | 95 ++++++++++++++++++++++ core/modules/system/system.module | 19 ++++- .../system/tests/modules/bc_test/bc_test.info.yml | 6 ++ .../system/tests/modules/bc_test/bc_test.module | 44 ++++++++++ .../tests/modules/common_test/common_test.module | 38 +++++++++ 6 files changed, 213 insertions(+), 11 deletions(-) diff --git a/core/includes/common.inc b/core/includes/common.inc index 16b1194..3eb59df 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -2473,19 +2473,25 @@ function drupal_prepare_page($page) { } // Modules can add attachments. - $pseudo_page_for_attachments = []; + $attachments = []; foreach (\Drupal::moduleHandler()->getImplementations('page_attachments') as $module) { $function = $module . '_page_attachments'; - $function($pseudo_page_for_attachments); + $function($attachments); + } + if (array_diff(array_keys($attachments), ['#attached', '#post_render_cache']) !== []) { + throw new \LogicException('Only #attached and #post_render_cache may be set in hook_page_attachments()'); } // Modules and themes can alter page attachments. - \Drupal::moduleHandler()->alter('page_attachments', $pseudo_page_for_attachments); - \Drupal::theme()->alter('page_attachments', $pseudo_page_for_attachments); - if (isset($pseudo_page_for_attachments['#attached'])) { - $page['#attached'] = $pseudo_page_for_attachments['#attached']; + \Drupal::moduleHandler()->alter('page_attachments', $attachments); + \Drupal::theme()->alter('page_attachments', $attachments); + if (array_diff(array_keys($attachments), ['#attached', '#post_render_cache']) !== []) { + throw new \LogicException('Only #attached and #post_render_cache may be set in hook_page_attachments_alter()'); + } + if (isset($attachments['#attached'])) { + $page['#attached'] = $attachments['#attached']; } - if (isset($pseudo_page_for_attachments['#post_render_cache'])) { - $page['#post_render_cache'] = $pseudo_page_for_attachments['#post_render_cache']; + if (isset($attachments['#post_render_cache'])) { + $page['#post_render_cache'] = $attachments['#post_render_cache']; } // Modules can add renderable arrays to the top and bottom of the page. diff --git a/core/modules/system/src/Tests/Common/PageRenderTest.php b/core/modules/system/src/Tests/Common/PageRenderTest.php new file mode 100644 index 0000000..cf8d848 --- /dev/null +++ b/core/modules/system/src/Tests/Common/PageRenderTest.php @@ -0,0 +1,95 @@ +enableModules(['common_test']); + $this->assertPageRenderHookExceptions('common_test', 'hook_page_attachments'); + } + + /** + * Tests hook_page_attachments_alter() exceptions. + */ + function testHookPageAlter() { + $this->enableModules(['common_test']); + $this->assertPageRenderHookExceptions('common_test', 'hook_page_attachments_alter'); + } + + /** + * Tests hook_page_build() exceptions, a deprecated hook kept around for BC. + */ + function testHookPageBuildExceptions() { + // Also enable the system module, because that module invokes the BC hooks. + $this->enableModules(['bc_test', 'system']); + $this->assertPageRenderHookExceptions('bc_test', 'hook_page_build'); + } + + /** + * Tests hook_page_alter(), a deprecated hook kept around for BC. + */ + function testHookPageAttachmentsAlter() { + // Also enable the system module, because that module invokes the BC hooks. + $this->enableModules(['bc_test', 'system']); + $this->assertPageRenderHookExceptions('bc_test', 'hook_page_alter'); + } + + /** + * Asserts whether expected exceptions are thrown for invalid hook implementations. + * + * @param string $module + * The module whose invalid logic in its hooks to enable. + * @param string $hook + * The page render hook to assert expected exceptions for. + */ + function assertPageRenderHookExceptions($module, $hook) { + // Assert a valid hook implementation doesn't trigger an exception. + $page = []; + drupal_prepare_page($page); + + // Assert an invalid hook implementation doesn't trigger an exception. + \Drupal::state()->set($module . '.' . $hook . '.descendant_attached', TRUE); + $assertion = $hook . '() implementation that sets #attached on a descendant triggers an exception'; + $page = []; + try { + drupal_prepare_page($page); + $this->error($assertion); + } + catch (\LogicException $e) { + $this->pass($assertion); + $this->assertEqual($e->getMessage(), 'Only #attached and #post_render_cache may be set in ' . $hook . '()'); + } + \Drupal::state()->set('bc_test.' . $hook . '.descendant_attached', FALSE); + + // Assert an invalid hook implementation doesn't trigger an exception. + \Drupal::state()->set('bc_test.' . $hook . '.render_array', TRUE); + $assertion = $hook . '() implementation that sets a child render array triggers an exception'; + $page = []; + try { + drupal_prepare_page($page); + $this->error($assertion); + } + catch (\LogicException $e) { + $this->pass($assertion); + $this->assertEqual($e->getMessage(), 'Only #attached and #post_render_cache may be set in ' . $hook . '()'); + } + \Drupal::state()->set($module . '.' . $hook . '.render_array', FALSE); + } + +} diff --git a/core/modules/system/system.module b/core/modules/system/system.module index 41ad2c0..d201cbe 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -587,12 +587,25 @@ function system_page_attachments(array &$page) { // Invoke hook_page_build() for modules and hook_page_alter() for both modules // and, for backwards compatibility. + $attachments = []; foreach (\Drupal::moduleHandler()->getImplementations('page_build') as $module) { $function = $module . '_page_build'; - $function($page); + $function($attachments); + } + if (array_diff(array_keys($attachments), ['#attached', '#post_render_cache']) !== []) { + throw new \LogicException('Only #attached and #post_render_cache may be set in hook_page_build()'); + } + \Drupal::moduleHandler()->alter('page', $attachments); + \Drupal::theme()->alter('page', $attachments); + if (array_diff(array_keys($attachments), ['#attached', '#post_render_cache']) !== []) { + throw new \LogicException('Only #attached and #post_render_cache may be set in hook_page_alter()'); + } + if (isset($attachments['#attached'])) { + $page['#attached'] = $attachments['#attached']; + } + if (isset($attachments['#post_render_cache'])) { + $page['#post_render_cache'] = $attachments['#post_render_cache']; } - \Drupal::moduleHandler()->alter('page', $page); - \Drupal::theme()->alter('page', $page); } /** diff --git a/core/modules/system/tests/modules/bc_test/bc_test.info.yml b/core/modules/system/tests/modules/bc_test/bc_test.info.yml new file mode 100644 index 0000000..20dbd82 --- /dev/null +++ b/core/modules/system/tests/modules/bc_test/bc_test.info.yml @@ -0,0 +1,6 @@ +name: 'Backwards Compatibility Test' +type: module +description: 'Support module for backwards compatibility tests.' +package: Testing +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/bc_test/bc_test.module b/core/modules/system/tests/modules/bc_test/bc_test.module new file mode 100644 index 0000000..537acb8 --- /dev/null +++ b/core/modules/system/tests/modules/bc_test/bc_test.module @@ -0,0 +1,44 @@ +get('bc_test.hook_page_build.descendant_attached', FALSE)) { + $page['content']['#attached']['library'][] = 'core/jquery'; + } + + if (\Drupal::state()->get('bc_test.hook_page_build.render_array', FALSE)) { + $page['something'] = [ + '#markup' => 'test', + ]; + } +} + +/** + * Implements hook_page_alter(). + * + * @see \Drupal\system\Tests\Common\PageRenderTest::assertPageRenderHookExceptions() + */ +function bc_test_page_alter(&$page) { + $page['#attached']['library'][] = 'core/jquery'; + + if (\Drupal::state()->get('bc_test.hook_page_alter.descendant_attached', FALSE)) { + $page['content']['#attached']['library'][] = 'core/jquery'; + } + + if (\Drupal::state()->get('bc_test.hook_page_alter.render_array', FALSE)) { + $page['something'] = [ + '#markup' => 'test', + ]; + } +} diff --git a/core/modules/system/tests/modules/common_test/common_test.module b/core/modules/system/tests/modules/common_test/common_test.module index e812b61..a9de4cc 100644 --- a/core/modules/system/tests/modules/common_test/common_test.module +++ b/core/modules/system/tests/modules/common_test/common_test.module @@ -255,3 +255,41 @@ function common_test_post_render_cache_placeholder(array $element, array $contex return $element; } + +/** + * Implements hook_page_attachments(). + * + * @see \Drupal\system\Tests\Common\PageRenderTest::assertPageRenderHookExceptions() + */ +function common_test_page_attachments(array &$page) { + $page['#attached']['library'][] = 'core/jquery'; + + if (\Drupal::state()->get('common_test.hook_page_attachments.descendant_attached', FALSE)) { + $page['content']['#attached']['library'][] = 'core/jquery'; + } + + if (\Drupal::state()->get('common_test.hook_page_attachments.render_array', FALSE)) { + $page['something'] = [ + '#markup' => 'test', + ]; + } +} + +/** + * Implements hook_page_attachments_alter(). + * + * @see \Drupal\system\Tests\Common\PageRenderTest::assertPageRenderHookExceptions() + */ +function common_test_page_attachments_alter(array &$page) { + $page['#attached']['library'][] = 'core/jquery'; + + if (\Drupal::state()->get('common_test.hook_page_attachments_alter.descendant_attached', FALSE)) { + $page['content']['#attached']['library'][] = 'core/jquery'; + } + + if (\Drupal::state()->get('common_test.hook_page_attachments_alter.render_array', FALSE)) { + $page['something'] = [ + '#markup' => 'test', + ]; + } +}