diff --git a/composer.json b/composer.json index 26d4063..f0b4f77 100644 --- a/composer.json +++ b/composer.json @@ -8,5 +8,8 @@ "documentation": "https://api.drupal.org/api/examples", "source": "http://cgit.drupalcode.org/examples" }, + "suggest": { + "drupal/devel": "Some modules will be able to pretty-print PHP with this module." + }, "license": "GPL-2.0+" } diff --git a/render_example/config/install/render_example.settings.yml b/render_example/config/install/render_example.settings.yml new file mode 100644 index 0000000..f1df332 --- /dev/null +++ b/render_example/config/install/render_example.settings.yml @@ -0,0 +1,6 @@ +show_block: false +show_page: false +note_about_render_arrays: false +move_breadcrumbs: false +reverse_sidebar: false +wrap_blocks: false diff --git a/render_example/config/schema/render_example.schema.yml b/render_example/config/schema/render_example.schema.yml new file mode 100644 index 0000000..ca8b19c --- /dev/null +++ b/render_example/config/schema/render_example.schema.yml @@ -0,0 +1,16 @@ +render_example.settings: + type: config_object + label: 'Render Example Settings' + mapping: + show_block: + type: boolean + show_page: + type: boolean + note_about_render_arrays: + type: boolean + move_breadcrumbs: + type: boolean + reverse_sidebar: + type: boolean + wrap_blocks: + type: boolean diff --git a/render_example/render_example.info.yml b/render_example/render_example.info.yml index 79238fa..ddf0447 100644 --- a/render_example/render_example.info.yml +++ b/render_example/render_example.info.yml @@ -4,4 +4,7 @@ description: Provides examples demonstrating Drupal's Render API. package: Example modules core: 8.x dependencies: + - core:block + - core:node + - core:user - examples:examples diff --git a/render_example/render_example.links.menu.yml b/render_example/render_example.links.menu.yml new file mode 100644 index 0000000..76afbc3 --- /dev/null +++ b/render_example/render_example.links.menu.yml @@ -0,0 +1,19 @@ +render_example.description: + title: Render Example + description: Examples of building and altering render arrays. + route_name: render_example.description + expanded: TRUE + +render_example.altering: + title: Altering Render Arrays + description: Using hooks and callbacks to alter render arrays. + route_name: render_example.altering + parent: render_example.description + weight: -9 + +render_example.arrays: + title: Building Render Arrays + description: Building render arrays in controllers. + route_name: render_example.arrays + parent: render_example.description + weight: -8 diff --git a/render_example/render_example.module b/render_example/render_example.module index 3cfd633..79524b4 100644 --- a/render_example/render_example.module +++ b/render_example/render_example.module @@ -140,6 +140,11 @@ function render_example_add_suffix(array $element) { * represents the page currently being viewed. */ function render_example_preprocess_page(&$variables) { + // Only modify the 'altering' page. + if (\Drupal::routeMatch()->getRouteName() != 'render_example.altering') { + return; + } + $config = \Drupal::config('render_example.settings'); // Preprocess hooks are invoked by the theme layer, and are used to give @@ -159,12 +164,8 @@ function render_example_preprocess_page(&$variables) { // printing it to the screen. $page = &$variables['page']; - if (\Drupal::routeMatch()->getRouteName() == 'render_example.altering') { - - } - // Move the breadcrumbs into the content area. - if ($config->get('render_example.move_breadcrumbs') && !empty($page['breadcrumb']) && !empty($page['content'])) { + if ($config->get('move_breadcrumbs') && !empty($page['breadcrumb']) && !empty($page['content'])) { $page['content']['breadcrumb'] = $page['breadcrumb']; unset($page['breadcrumb']); $page['content']['breadcrumb']['#weight'] = -99999; @@ -174,7 +175,7 @@ function render_example_preprocess_page(&$variables) { } // Re-sort the contents of the sidebar in reverse order. - if ($config->get('render_example.reverse_sidebar') && !empty($page['sidebar_first'])) { + if ($config->get('reverse_sidebar') && !empty($page['sidebar_first'])) { $page['sidebar_first'] = array_reverse($page['sidebar_first']); foreach (Element::children($page['sidebar_first']) as $element) { // Reverse the weights if they exist. @@ -189,7 +190,7 @@ function render_example_preprocess_page(&$variables) { // Show the render array used to build the current page. // This relies on the Devel module's variable dumper service. // https://wwww.drupal.org/project/devel - if (Drupal::moduleHandler()->moduleExists('devel') && $config->get('render_example.show_page')) { + if (Drupal::moduleHandler()->moduleExists('devel') && $config->get('show_page')) { $page['content']['page_render_array'] = [ '#type' => 'markup', '#prefix' => '

' . t('The page render array') . '

', @@ -208,19 +209,24 @@ function render_example_preprocess_page(&$variables) { * Implements hook_preprocess_block(). */ function render_example_preprocess_block(&$variables) { + // Only modify the 'altering' page. + if (\Drupal::routeMatch()->getRouteName() != 'render_example.altering') { + return; + } + $config = \Drupal::config('render_example.settings'); // This example shows how you can manipulate an existing renderable array. In // this case by adding #prefix and #suffix properties to the block in order to // wrap a
around it. - if ($config->get('render_example.prefix')) { + if ($config->get('wrap_blocks')) { $variables['content']['#prefix'] = '

' . t('Prefixed') . '

'; $variables['content']['#suffix'] = '' . t('Block suffix') . '
'; } // Show the render array used to build each block if the Devel module is // installed and the feature is enabled. - if (Drupal::moduleHandler()->moduleExists('devel') && $config->get('render_example.show_block')) { + if (Drupal::moduleHandler()->moduleExists('devel') && $config->get('show_block')) { $variables['content']['block_render_array'] = [ '#type' => 'markup', '#prefix' => '

' . t('The block render array for @block_id.', ['@block_id' => $variables['plugin_id']]) . '

', diff --git a/render_example/render_example.routing.yml b/render_example/render_example.routing.yml index fff4a60..5f93efa 100644 --- a/render_example/render_example.routing.yml +++ b/render_example/render_example.routing.yml @@ -1,21 +1,21 @@ render_example.description: - path: 'examples/render_example' + path: 'examples/render-example' defaults: _controller: '\Drupal\render_example\Controller\RenderExampleController::description' requirements: - _access: 'access content' + _permission: 'access content' render_example.altering: - path: 'examples/render_example/altering' + path: 'examples/render-example/altering' defaults: _form: '\Drupal\render_example\Form\RenderExampleDemoForm' _title: 'Alter pages and blocks' requirements: - _access: 'access content' + _permission: 'access content' render_example.arrays: - path: 'examples/render_example/arrays' + path: 'examples/render-example/arrays' defaults: _controller: '\Drupal\render_example\Controller\RenderExampleController::arrays' requirements: - _access: 'access content' + _permission: 'access content' diff --git a/render_example/src/Controller/RenderExampleController.php b/render_example/src/Controller/RenderExampleController.php index 694f2f8..61ea14e 100644 --- a/render_example/src/Controller/RenderExampleController.php +++ b/render_example/src/Controller/RenderExampleController.php @@ -20,15 +20,8 @@ use Drupal\Core\Session\AccountInterface; * @ingroup render_example */ class RenderExampleController extends ControllerBase { - use DescriptionTemplateTrait; - - /** - * Current user. - * - * @var \Drupal\user\UserInterface - */ - protected $currentUser; + use DescriptionTemplateTrait; /** * Constructs a new BlockController instance. @@ -422,23 +415,25 @@ class RenderExampleController extends ControllerBase { foreach (Element::children($element) as $key) { $child = $element[$key]; unset($element[$key]); - $element[$key] = [ - // The value from the #description property will be used as a title - // for this element in the final output. - 'description' => [ - '#markup' => $child['#description'], - ], - // Move the original element to 'rendered'. The rendering process is - // recursive so this will still be located, and rendered to HTML. - 'rendered' => $child, - // Export the element definition as a string of text so we can display - // the array that was used to create the rendered output just below the - // output. - 'unrendered' => [ - '#markup' => htmlentities(Variable::export($child)), - ], - '#theme' => 'render_array', - ]; + if (isset($child['#description'])) { + $element[$key] = [ + // The value from the #description property will be used as a title + // for this element in the final output. + 'description' => [ + '#markup' => $child['#description'], + ], + // Move the original element to 'rendered'. The rendering process is + // recursive so this will still be located, and rendered to HTML. + 'rendered' => $child, + // Export the element definition as a string of text so we can display + // the array that was used to create the rendered output just below the + // output. + 'unrendered' => [ + '#markup' => htmlentities(Variable::export($child)), + ], + '#theme' => 'render_array', + ]; + } } // Return our modified version of the original $element. diff --git a/render_example/src/Element/Marquee.php b/render_example/src/Element/Marquee.php index 3ec03e7..1a5c301 100644 --- a/render_example/src/Element/Marquee.php +++ b/render_example/src/Element/Marquee.php @@ -40,7 +40,6 @@ class Marquee extends RenderElement { * {@inheritdoc} */ public function getInfo() { - $class = get_class($this); // Returns an array of default properties that will be merged with any // properties defined in a render array when using this element type. @@ -52,7 +51,7 @@ class Marquee extends RenderElement { // Define a default #pre_render method. We will use this to handle // additional processing for the custom attributes we add below. '#pre_render' => [ - [$class, 'preRenderMarquee'], + [self::class, 'preRenderMarquee'], ], // This is a custom property for our element type. We set it to blank by // default. The expectation is that a user will add the content that they diff --git a/render_example/src/Form/RenderExampleDemoForm.php b/render_example/src/Form/RenderExampleDemoForm.php index 1460a4c..539c0be 100644 --- a/render_example/src/Form/RenderExampleDemoForm.php +++ b/render_example/src/Form/RenderExampleDemoForm.php @@ -8,6 +8,7 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Config\ConfigFactoryInterface; use Symfony\Component\DependencyInjection\ContainerInterface; + /** * Provides the form for toggling module features on and off. * @@ -63,14 +64,14 @@ class RenderExampleDemoForm extends ConfigFormBase { 'render_example_show_block' => [ '#type' => 'checkbox', '#title' => $this->t('Show block render arrays'), - '#default_value' => $config->get('render_example.show_block'), + '#default_value' => $config->get('show_block'), // Only enable this option if the Devel module is enabled. '#access' => $this->moduleHandler->moduleExists('devel'), ], 'render_example_show_page' => [ '#type' => 'checkbox', '#title' => $this->t('Show page render arrays'), - '#default_value' => $config->get('render_example.show_page'), + '#default_value' => $config->get('show_page'), // Only enable this option if the Devel module is enabled. '#access' => $this->moduleHandler->moduleExists('devel'), ], @@ -89,19 +90,19 @@ class RenderExampleDemoForm extends ConfigFormBase { '#title' => t('Move the breadcrumbs to the top of the content area'), '#description' => t('Uses hook_preprocess_page() to move the breadcrumbs into another region.'), '#type' => 'checkbox', - '#default_value' => $config->get('render_example.move_breadcrumbs'), + '#default_value' => $config->get('move_breadcrumbs'), ], 'render_example_reverse_sidebar' => [ '#title' => t('Reverse ordering of sidebar_first elements (if it exists)'), '#description' => t('Uses hook_preprocess_page() to reverse the ordering of items in sidebar_first'), '#type' => 'checkbox', - '#default_value' => $config->get('render_example.reverse_sidebar'), + '#default_value' => $config->get('reverse_sidebar'), ], - 'render_example_prefix' => [ + 'render_example_wrap_blocks' => [ '#title' => t('Use #prefix and #suffix to wrap a div around every block'), '#description' => t('Uses hook_block_view_alter() to wrap all blocks with a div using #prefix and #suffix'), '#type' => 'checkbox', - '#default_value' => $config->get('render_example.prefix'), + '#default_value' => $config->get('wrap_blocks'), ], ]; @@ -273,12 +274,11 @@ class RenderExampleDemoForm extends ConfigFormBase { $values = $form_state->getValues(); $config = $this->config('render_example.settings'); - $config->set('render_example.show_block', $values['render_example_show_block'])->save(); - $config->set('render_example.show_page', $values['render_example_show_page'])->save(); - $config->set('render_example.note_about_render_arrays', $values['render_example_note_about_render_arrays'])->save(); - $config->set('render_example.move_breadcrumbs', $values['render_example_move_breadcrumbs'])->save(); - $config->set('render_example.reverse_sidebar', $values['render_example_reverse_sidebar'])->save(); - $config->set('render_example.prefix', $values['render_example_prefix'])->save(); + $config->set('show_block', $values['render_example_show_block'])->save(); + $config->set('show_page', $values['render_example_show_page'])->save(); + $config->set('move_breadcrumbs', $values['render_example_move_breadcrumbs'])->save(); + $config->set('reverse_sidebar', $values['render_example_reverse_sidebar'])->save(); + $config->set('wrap_blocks', $values['render_example_wrap_blocks'])->save(); parent::submitForm($form, $form_state); } diff --git a/render_example/src/Tests/RenderExampleTest.php b/render_example/src/Tests/RenderExampleTest.php deleted file mode 100644 index c292887..0000000 --- a/render_example/src/Tests/RenderExampleTest.php +++ /dev/null @@ -1,142 +0,0 @@ -xpath($xpath); - $this->assertTrue(!empty($result), format_string('Found xpath %xpath', ['%xpath' => $xpath])); - } - } - - /** - * Asserts that the string value of the result is the same as the passed text. - * - * @param string[] $xpath_array - * Array of keyed arrays of tests to be made. Each child array consists of - * $xpath => $expected_text. - */ - protected function assertRenderedText(array $xpath_array) { - foreach ($xpath_array as $xpath => $text) { - $result = $this->xpath($xpath); - $this->assertTrue((string) $result[0][0] == $text, format_string('%ary selects text %text', [ - '%ary' => $xpath, - '%text' => $text, - ])); - } - } - - /** - * Basic functional test of render_example module. - * - * - Login user. - * - Create an example node. - * - Test blog functionality through the admin and user interfaces. - */ - public function testRenderExampleBasic() { - // Create a user that can access devel information and log in. - $web_user = $this->drupalCreateUser([ - 'access devel information', - 'access content', - ]); - $this->drupalLogin($web_user); - - // Turn on the block render array display and make sure it shows up. - $edit = [ - 'render_example_show_block' => TRUE, - ]; - $this->drupalPost('examples/render_example/altering', $edit, t('Save configuration')); - - $xpath_array = [ - "//div[@id='sidebar-first']//fieldset[starts-with(@id, 'edit-render-example-block-fieldset')]", - '//*[@id="content"]//fieldset[contains(@id,"edit-render-example-block-fieldset")]', - ]; - $this->assertRenderResults($xpath_array); - - // Turn off block render array display and turn on the page render array - // display. - $edit = [ - 'render_example_show_page' => TRUE, - 'render_example_show_block' => FALSE, - ]; - $this->drupalPost('examples/render_example/altering', $edit, t('Save configuration')); - - $xpath_array = [ - '//*[@id="content"]//fieldset[starts-with(@id,"edit-render-example-page-fieldset")]', - ]; - $this->assertRenderResults($xpath_array); - - // Add note about render arrays to the top of sidebar_first. - $edit = [ - 'render_example_note_about_render_arrays' => TRUE, - ]; - $this->drupalPost('examples/render_example/altering', $edit, t('Save configuration')); - $xpath_array = [ - '//*[@id="sidebar-first"]//ol//li[starts-with(.,"Render arrays are everywhere")]', - ]; - $this->assertRenderResults($xpath_array); - - // Move the navigation menu to the top of the content area. - $edit = [ - 'render_example_move_navigation_menu' => TRUE, - ]; - $this->drupalPost('examples/render_example/altering', $edit, t('Save configuration')); - $xpath_array = [ - '//*[@id="content"]//h2[starts-with(.,"Navigation")]', - ]; - $this->assertRenderResults($xpath_array); - - // Skip a test for reversing order of sidebar_first as I think it would - // be too fragile. - // Test the addition of #prefix and #suffix. - $edit = [ - 'render_example_prefix' => TRUE, - ]; - $this->drupalPost('examples/render_example/altering', $edit, t('Save configuration')); - $xpath_array = [ - '//*[@id="sidebar-first"]//*[contains(@class, "block-prefix")]/span[contains(@class, "block-suffix")]', - ]; - $this->assertRenderResults($xpath_array); - - // Test some rendering facets of the various render examples. - $this->drupalGet('examples/render_example/arrays'); - $content = $this->xpath('//*[@class="render-array"][1]'); - - $xpath_array = [ - '//div[@class="rendered"][starts-with(.,"Some basic text in a #markup")]' => 'Some basic text in a #markup (shows basic markup and how it is rendered)', - '//div[@class="rendered"][starts-with(.,"This is some text that should be put to")]' => 'This is some text that should be put together | This is some more text that we need | ', - '//div[@class="rendered"][starts-with(.,"The current time was")]' => 'The current time was when this was cached. Updated every seconds', - '//div[@class="rendered"]/div[text()][starts-with(.,"(prefix)This one")]' => '(prefix)This one adds a prefix and suffix, which put a div around the item(suffix)', - '//div[@class="rendered"]/div[text()][starts-with(.,"markup for pre_")]' => 'markup for pre_render and post_render example', - '//div[@class="rendered"]/div[text()][starts-with(.,"This markup was added")]' => 'This markup was added after rendering by a #post_render', - '//div[@class="rendered"]/div[text()][starts-with(.,"This #suffix")]' => 'This #suffix was added by a #pre_render', - ]; - $this->assertRenderedText($xpath_array); - } - -} diff --git a/render_example/tests/src/Functional/RenderExampleMenuTest.php b/render_example/tests/src/Functional/RenderExampleMenuTest.php new file mode 100644 index 0000000..f2dc164 --- /dev/null +++ b/render_example/tests/src/Functional/RenderExampleMenuTest.php @@ -0,0 +1,64 @@ +drupalLogin( + $this->createUser(['access content']) + ); + + $assertion = $this->assertSession(); + + // Get the front page, which should only have the links in the sidebar. + $this->drupalGet(''); + foreach ($links as $path) { + $assertion->linkByHrefExists($path); + } + + // Get each path and verify a 200 response. + foreach ($links as $path) { + $this->drupalGet($path); + $assertion->statusCodeEquals(200); + } + } + +} diff --git a/render_example/tests/src/Functional/RenderExampleTest.php b/render_example/tests/src/Functional/RenderExampleTest.php new file mode 100644 index 0000000..874d074 --- /dev/null +++ b/render_example/tests/src/Functional/RenderExampleTest.php @@ -0,0 +1,126 @@ +createUser([ + 'access content', + ]); + $this->drupalLogin($web_user); + + $session = $this->assertSession(); + + $this->drupalGet('examples/render-example/altering'); + // Make sure we're telling the user about devel. + $session->pageTextContains('Install the Devel module (https://www.drupal.org/project/devel) to enable additional demonstration features.'); + + // Test moving the breadcrumb to the top of the content region. Since we + // just installed render_example and the config defaults to FALSE for all + // the alter options, we shouldn't have to manage state before making + // assertions. + $breadcrumb_xpath = "//main[@id='content']//div[contains(@class, 'block-system-breadcrumb-block')]"; + $this->assertEmpty($this->xpath($breadcrumb_xpath)); + // Move the breadcrumbs to content region. + $this->drupalPostForm( + 'examples/render-example/altering', + [ + 'render_example_move_breadcrumbs' => TRUE, + 'render_example_reverse_sidebar' => FALSE, + 'render_example_wrap_blocks' => FALSE, + ], + t('Save configuration') + ); + $this->assertNotEmpty($this->xpath($breadcrumb_xpath)); + + // Test reversing order of items in region sidebar-first. Get the node + // elements under the sidebar region div. + $breadcrumb_xpath = "//div[contains(@class,'region-sidebar-first')]/*"; + $elements = $this->xpath($breadcrumb_xpath); + // There should be two elements, a div and then a nav. + $this->assertEquals('div', $elements[0]->getTagName()); + $this->assertTrue($elements[0]->hasClass('block-search')); + $this->assertEquals('nav', $elements[1]->getTagName()); + $this->drupalPostForm( + 'examples/render-example/altering', + [ + 'render_example_move_breadcrumbs' => FALSE, + 'render_example_reverse_sidebar' => TRUE, + 'render_example_wrap_blocks' => FALSE, + ], + t('Save configuration') + ); + // Get the elements again. + $elements = $this->xpath($breadcrumb_xpath); + // There should be two elements, a nav and then a div. + $this->assertEquals('nav', $elements[0]->getTagName()); + $this->assertEquals('div', $elements[1]->getTagName()); + $this->assertTrue($elements[1]->hasClass('block-search')); + + // Test wrapping blocks in divs. + $xpath = "//div[contains(@class,'block')]//div[@class='content']/div[@class='block-prefix']"; + $this->assertEmpty($this->xpath($xpath)); + $this->drupalPostForm( + 'examples/render-example/altering', + [ + 'render_example_move_breadcrumbs' => FALSE, + 'render_example_reverse_sidebar' => FALSE, + 'render_example_wrap_blocks' => TRUE, + ], + t('Save configuration') + ); + $this->assertNotEmpty($this->xpath($xpath)); + + /* + // Test some rendering facets of the various render examples. + $this->drupalGet('examples/render-example/arrays'); + + $xpath_array = [ + 'foof' => 'Hello ' . $web_user->getAccountName() . ', welcome to the #cache example.', + '//div[@class="render_example--rendered"][starts-with(.,"Some basic text in a #markup")]' => 'Some basic text in a #markup (shows basic markup and how it is rendered)', + '//div[@class="render_example--rendered"][starts-with(.,"This is some text that should be put to")]' => 'This is some text that should be put together | This is some more text that we need | ', + '//div[@class="render_example--rendered"][starts-with(.,"The current time was")]' => 'The current time was when this was cached. Updated every seconds', + '//div[@class="render_example--rendered"]/div[text()][starts-with(.,"(prefix)This one")]' => '(prefix)This one adds a prefix and suffix, which put a div around the item(suffix)', + '//div[@class="render_example--rendered"]/div[text()][starts-with(.,"markup for pre_")]' => 'markup for pre_render and post_render example', + '//div[@class="render_example--rendered"]/div[text()][starts-with(.,"This markup was added")]' => 'This markup was added after rendering by a #post_render', + '//div[@class="render_example--rendered"]/div[text()][starts-with(.,"This #suffix")]' => 'This #suffix was added by a #pre_render', + ]; + foreach($xpath_array as $key => $value) { + $session->pageTextContains($value); + } + * + */ + } + +}