Example #1
1
  /**
   * Tests that #cache_properties are properly handled.
   *
   * @param array $expected_results
   *   An associative array of expected results keyed by property name.
   *
   * @covers ::render
   * @covers ::doRender
   * @covers \Drupal\Core\Render\RenderCache::get
   * @covers \Drupal\Core\Render\RenderCache::set
   * @covers \Drupal\Core\Render\RenderCache::createCacheID
   * @covers \Drupal\Core\Render\RenderCache::getCacheableRenderArray
   *
   * @dataProvider providerTestRenderCacheProperties
   */
  public function testRenderCacheProperties(array $expected_results) {
    $this->setUpRequest();
    $this->setupMemoryCache();

    $element = $original = [
      '#cache' => [
        'keys' => ['render_cache_test'],
      ],
      // Collect expected property names.
      '#cache_properties' => array_keys(array_filter($expected_results)),
      'child1' => ['#markup' => Markup::create('1')],
      'child2' => ['#markup' => Markup::create('2')],
      // Mark the value as safe.
      '#custom_property' => Markup::create('custom_value'),
      '#custom_property_array' => ['custom value'],
    ];

    $this->renderer->renderRoot($element);

    $cache = $this->cacheFactory->get('render');
    $data = $cache->get('render_cache_test:en:stark')->data;

    // Check that parent markup is ignored when caching children's markup.
    $this->assertEquals($data['#markup'] === '', (bool) Element::children($data));

    // Check that the element properties are cached as specified.
    foreach ($expected_results as $property => $expected) {
      $cached = !empty($data[$property]);
      $this->assertEquals($cached, (bool) $expected);
      // Check that only the #markup key is preserved for children.
      if ($cached) {
        $this->assertEquals($data[$property], $original[$property]);
      }
    }
    // #custom_property_array can not be a safe_cache_property.
    $safe_cache_properties = array_diff(Element::properties(array_filter($expected_results)), ['#custom_property_array']);
    foreach ($safe_cache_properties as $cache_property) {
      $this->assertTrue(SafeMarkup::isSafe($data[$cache_property]), "$cache_property is marked as a safe string");
    }
  }
Example #2
0
 /**
  * @covers ::preRenderConditionalComments
  * @dataProvider providerPreRenderConditionalComments
  */
 public function testPreRenderConditionalComments($element, $expected, $set_safe = FALSE)
 {
     if ($set_safe) {
         $element['#prefix'] = Markup::create($element['#prefix']);
         $element['#suffix'] = Markup::create($element['#suffix']);
     }
     $this->assertEquals($expected, HtmlTag::preRenderConditionalComments($element));
 }
Example #3
0
 /**
  * Test that the token replacement in views works correctly.
  */
 public function testViewsTokenReplace()
 {
     $text = '{{ langcode__value }} means {{ langcode }}';
     $tokens = ['{{ langcode }}' => Markup::create('English'), '{{ langcode__value }}' => 'en'];
     $result = \Drupal::service('renderer')->executeInRenderContext(new RenderContext(), function () use($text, $tokens) {
         return $this->testPluginBase->viewsTokenReplace($text, $tokens);
     });
     $this->assertIdentical($result, 'en means English');
 }
 /**
  * Tests [entity:field_name] tokens.
  */
 public function testEntityFieldTokens()
 {
     // Create a node with a value in the text field and test its token.
     $format = FilterFormat::create(['format' => 'test', 'weight' => 1, 'filters' => ['filter_html_escape' => ['status' => TRUE]]]);
     $format->save();
     $entity = Node::create(['title' => 'Test node title', 'type' => 'article', 'test_field' => ['value' => 'foo', 'format' => $format->id()]]);
     $entity->save();
     $this->assertTokens('node', ['node' => $entity], ['test_field' => Markup::create('foo')]);
     // Create a node without a value in the text field and test its token.
     $entity = Node::create(['title' => 'Test node title', 'type' => 'article']);
     $entity->save();
     $this->assertNoTokens('node', ['node' => $entity], ['test_field']);
 }
 /**
  * Gets all BigPipe placeholder test cases.
  *
  * @param \Symfony\Component\DependencyInjection\ContainerInterface|null $container
  *   Optional. Necessary to get the embedded AJAX/HTML responses.
  * @param \Drupal\Core\Session\AccountInterface|null $user
  *   Optional. Necessary to get the embedded AJAX/HTML responses.
  *
  * @return \Drupal\big_pipe\Tests\BigPipePlaceholderTestCase[]
  */
 public static function cases(ContainerInterface $container = NULL, AccountInterface $user = NULL)
 {
     // Define the two types of cacheability that we expect to see. These will be
     // used in the expectations.
     $cacheability_depends_on_session_only = ['max-age' => 0, 'contexts' => ['session.exists']];
     $cacheability_depends_on_session_and_nojs_cookie = ['max-age' => 0, 'contexts' => ['session.exists', 'cookies:big_pipe_nojs']];
     // 1. Real-world example of HTML placeholder.
     $status_messages = new BigPipePlaceholderTestCase([], '<drupal-render-placeholder callback="Drupal\\Core\\Render\\Element\\StatusMessages::renderMessages" arguments="0" token="a8c34b5e"></drupal-render-placeholder>', ['#lazy_builder' => ['Drupal\\Core\\Render\\Element\\StatusMessages::renderMessages', [NULL]]]);
     $status_messages->bigPipePlaceholderId = 'callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args[0]&amp;token=a8c34b5e';
     $status_messages->bigPipePlaceholderRenderArray = ['#markup' => '<div data-big-pipe-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args[0]&amp;token=a8c34b5e"></div>', '#cache' => $cacheability_depends_on_session_and_nojs_cookie, '#attached' => ['library' => ['big_pipe/big_pipe'], 'drupalSettings' => ['bigPipePlaceholderIds' => ['callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args[0]&token=a8c34b5e' => TRUE]], 'big_pipe_placeholders' => ['callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args[0]&amp;token=a8c34b5e' => $status_messages->placeholderRenderArray]]];
     $status_messages->bigPipeNoJsPlaceholder = '<div data-big-pipe-nojs-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args[0]&amp;token=a8c34b5e"></div>';
     $status_messages->bigPipeNoJsPlaceholderRenderArray = ['#markup' => '<div data-big-pipe-nojs-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args[0]&amp;token=a8c34b5e"></div>', '#cache' => $cacheability_depends_on_session_and_nojs_cookie, '#attached' => ['big_pipe_nojs_placeholders' => ['<div data-big-pipe-nojs-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args[0]&amp;token=a8c34b5e"></div>' => $status_messages->placeholderRenderArray]]];
     if ($container && $user) {
         $status_messages->embeddedAjaxResponseCommands = [['command' => 'settings', 'settings' => ['ajaxPageState' => ['theme' => 'classy', 'libraries' => 'big_pipe/big_pipe,classy/base,classy/messages,core/drupal.active-link,core/html5shiv,core/normalize,system/base'], 'pluralDelimiter' => \Drupal\Core\StringTranslation\PluralTranslatableMarkup::DELIMITER, 'user' => ['uid' => '1', 'permissionsHash' => $container->get('user_permissions_hash_generator')->generate($user)]], 'merge' => TRUE], ['command' => 'add_css', 'data' => '<link rel="stylesheet" href="' . base_path() . 'core/themes/classy/css/components/messages.css?' . $container->get('state')->get('system.css_js_query_string') . '" media="all" />' . "\n"], ['command' => 'insert', 'method' => 'replaceWith', 'selector' => '[data-big-pipe-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args[0]&token=a8c34b5e"]', 'data' => "\n" . '    <div role="contentinfo" aria-label="Status message" class="messages messages--status">' . "\n" . '                  <h2 class="visually-hidden">Status message</h2>' . "\n" . '                    Hello from BigPipe!' . "\n" . '            </div>' . "\n    \n", 'settings' => NULL]];
         $status_messages->embeddedHtmlResponse = '<link rel="stylesheet" href="' . base_path() . 'core/themes/classy/css/components/messages.css?' . $container->get('state')->get('system.css_js_query_string') . '" media="all" />' . "\n" . "\n" . '    <div role="contentinfo" aria-label="Status message" class="messages messages--status">' . "\n" . '                  <h2 class="visually-hidden">Status message</h2>' . "\n" . '                    Hello from BigPipe!' . "\n" . '            </div>' . "\n    \n";
     }
     // 2. Real-world example of HTML attribute value placeholder: form action.
     $form_action = new BigPipePlaceholderTestCase($container ? $container->get('form_builder')->getForm('Drupal\\big_pipe_test\\Form\\BigPipeTestForm') : [], 'form_action_cc611e1d', ['#lazy_builder' => ['form_builder:renderPlaceholderFormAction', []]]);
     $form_action->bigPipeNoJsPlaceholder = 'big_pipe_nojs_placeholder_attribute_safe:form_action_cc611e1d';
     $form_action->bigPipeNoJsPlaceholderRenderArray = ['#markup' => 'big_pipe_nojs_placeholder_attribute_safe:form_action_cc611e1d', '#cache' => $cacheability_depends_on_session_only, '#attached' => ['big_pipe_nojs_placeholders' => ['big_pipe_nojs_placeholder_attribute_safe:form_action_cc611e1d' => $form_action->placeholderRenderArray]]];
     if ($container) {
         $form_action->embeddedHtmlResponse = '<form class="big-pipe-test-form" data-drupal-selector="big-pipe-test-form" action="' . base_path() . 'big_pipe_test"';
     }
     // 3. Real-world example of HTML attribute value subset placeholder: CSRF
     // token in link.
     $csrf_token = new BigPipePlaceholderTestCase(['#title' => 'Link with CSRF token', '#type' => 'link', '#url' => Url::fromRoute('system.theme_set_default')], 'e88b559cce72c80b687d56b0e2a3a5ae4b66bc0e', ['#lazy_builder' => ['route_processor_csrf:renderPlaceholderCsrfToken', ['admin/config/user-interface/shortcut/manage/default/add-link-inline']]]);
     $csrf_token->bigPipeNoJsPlaceholder = 'big_pipe_nojs_placeholder_attribute_safe:e88b559cce72c80b687d56b0e2a3a5ae4b66bc0e';
     $csrf_token->bigPipeNoJsPlaceholderRenderArray = ['#markup' => 'big_pipe_nojs_placeholder_attribute_safe:e88b559cce72c80b687d56b0e2a3a5ae4b66bc0e', '#cache' => $cacheability_depends_on_session_only, '#attached' => ['big_pipe_nojs_placeholders' => ['big_pipe_nojs_placeholder_attribute_safe:e88b559cce72c80b687d56b0e2a3a5ae4b66bc0e' => $csrf_token->placeholderRenderArray]]];
     if ($container) {
         $csrf_token->embeddedHtmlResponse = $container->get('csrf_token')->get('admin/appearance/default');
     }
     // 4. Edge case: custom string to be considered as a placeholder that
     // happens to not be valid HTML.
     $hello = new BigPipePlaceholderTestCase(['#markup' => Markup::create('<hello'), '#attached' => ['placeholders' => ['<hello' => ['#lazy_builder' => ['\\Drupal\\big_pipe_test\\BigPipeTestController::helloOrYarhar', []]]]]], '<hello', ['#lazy_builder' => ['hello_or_yarhar', []]]);
     $hello->bigPipeNoJsPlaceholder = 'big_pipe_nojs_placeholder_attribute_safe:&lt;hello';
     $hello->bigPipeNoJsPlaceholderRenderArray = ['#markup' => 'big_pipe_nojs_placeholder_attribute_safe:&lt;hello', '#cache' => $cacheability_depends_on_session_only, '#attached' => ['big_pipe_nojs_placeholders' => ['big_pipe_nojs_placeholder_attribute_safe:&lt;hello' => $hello->placeholderRenderArray]]];
     $hello->embeddedHtmlResponse = '<marquee>Yarhar llamas forever!</marquee>';
     // 5. Edge case: non-#lazy_builder placeholder.
     $current_time = new BigPipePlaceholderTestCase(['#markup' => Markup::create('<time>CURRENT TIME</time>'), '#attached' => ['placeholders' => ['<time>CURRENT TIME</time>' => ['#pre_render' => ['\\Drupal\\big_pipe_test\\BigPipeTestController::currentTime']]]]], '<time>CURRENT TIME</time>', ['#pre_render' => ['current_time']]);
     $current_time->bigPipePlaceholderId = 'timecurrent-timetime';
     $current_time->bigPipePlaceholderRenderArray = ['#markup' => '<div data-big-pipe-placeholder-id="timecurrent-timetime"></div>', '#cache' => $cacheability_depends_on_session_and_nojs_cookie, '#attached' => ['library' => ['big_pipe/big_pipe'], 'drupalSettings' => ['bigPipePlaceholderIds' => ['timecurrent-timetime' => TRUE]], 'big_pipe_placeholders' => ['timecurrent-timetime' => $current_time->placeholderRenderArray]]];
     $current_time->embeddedAjaxResponseCommands = [['command' => 'insert', 'method' => 'replaceWith', 'selector' => '[data-big-pipe-placeholder-id="timecurrent-timetime"]', 'data' => '<time datetime=1991-03-14"></time>', 'settings' => NULL]];
     $current_time->bigPipeNoJsPlaceholder = '<div data-big-pipe-nojs-placeholder-id="timecurrent-timetime"></div>';
     $current_time->bigPipeNoJsPlaceholderRenderArray = ['#markup' => '<div data-big-pipe-nojs-placeholder-id="timecurrent-timetime"></div>', '#cache' => $cacheability_depends_on_session_and_nojs_cookie, '#attached' => ['big_pipe_nojs_placeholders' => ['<div data-big-pipe-nojs-placeholder-id="timecurrent-timetime"></div>' => $current_time->placeholderRenderArray]]];
     $current_time->embeddedHtmlResponse = '<time datetime=1991-03-14"></time>';
     return ['html' => $status_messages, 'html_attribute_value' => $form_action, 'html_attribute_value_subset' => $csrf_token, 'edge_case__invalid_html' => $hello, 'edge_case__html_non_lazy_builder' => $current_time];
 }
Example #6
0
 /**
  * Sets up the unrouted url assembler and the link generator.
  */
 protected function setUpUrlIntegrationServices()
 {
     $this->pathProcessor = $this->getMock('Drupal\\Core\\PathProcessor\\OutboundPathProcessorInterface');
     $this->unroutedUrlAssembler = new UnroutedUrlAssembler($this->requestStack, $this->pathProcessor);
     \Drupal::getContainer()->set('unrouted_url_assembler', $this->unroutedUrlAssembler);
     $this->linkGenerator = new LinkGenerator($this->urlGenerator, $this->getMock('Drupal\\Core\\Extension\\ModuleHandlerInterface'), $this->renderer);
     $this->renderer->method('render')->willReturnCallback(function (&$elements, $is_root_call = FALSE) {
         // Mock the ability to theme links
         $link = $this->linkGenerator->generate($elements['#title'], $elements['#url']);
         if (isset($elements['#prefix'])) {
             $link = $elements['#prefix'] . $link;
         }
         if (isset($elements['#suffix'])) {
             $link = $link . $elements['#suffix'];
         }
         return Markup::create($link);
     });
 }
 /**
  * Wrapper for \Drupal\Core\Render\Markup::create().
  *
  * @param string $input
  *   The input string to mark as safe.
  *
  * @return string
  *   The unaltered input value.
  */
 protected function setSafeMarkup($input)
 {
     return Markup::create($input);
 }
Example #8
0
 /**
  * Pre-render callback: Renders #browsers into #prefix and #suffix.
  *
  * @param array $element
  *   A render array with a '#browsers' property. The '#browsers' property can
  *   contain any or all of the following keys:
  *   - 'IE': If FALSE, the element is not rendered by Internet Explorer. If
  *     TRUE, the element is rendered by Internet Explorer. Can also be a string
  *     containing an expression for Internet Explorer to evaluate as part of a
  *     conditional comment. For example, this can be set to 'lt IE 7' for the
  *     element to be rendered in Internet Explorer 6, but not in Internet
  *     Explorer 7 or higher. Defaults to TRUE.
  *   - '!IE': If FALSE, the element is not rendered by browsers other than
  *     Internet Explorer. If TRUE, the element is rendered by those browsers.
  *     Defaults to TRUE.
  *   Examples:
  *   - To render an element in all browsers, '#browsers' can be left out or set
  *     to array('IE' => TRUE, '!IE' => TRUE).
  *   - To render an element in Internet Explorer only, '#browsers' can be set
  *     to array('!IE' => FALSE).
  *   - To render an element in Internet Explorer 6 only, '#browsers' can be set
  *     to array('IE' => 'lt IE 7', '!IE' => FALSE).
  *   - To render an element in Internet Explorer 8 and higher and in all other
  *     browsers, '#browsers' can be set to array('IE' => 'gte IE 8').
  *
  * @return array
  *   The passed-in element with markup for conditional comments potentially
  *   added to '#prefix' and '#suffix'.
  */
 public static function preRenderConditionalComments($element)
 {
     $browsers = isset($element['#browsers']) ? $element['#browsers'] : array();
     $browsers += array('IE' => TRUE, '!IE' => TRUE);
     // If rendering in all browsers, no need for conditional comments.
     if ($browsers['IE'] === TRUE && $browsers['!IE']) {
         return $element;
     }
     // Determine the conditional comment expression for Internet Explorer to
     // evaluate.
     if ($browsers['IE'] === TRUE) {
         $expression = 'IE';
     } elseif ($browsers['IE'] === FALSE) {
         $expression = '!IE';
     } else {
         // The IE expression might contain some user input data.
         $expression = Xss::filterAdmin($browsers['IE']);
     }
     // If the #prefix and #suffix properties are used, wrap them with
     // conditional comment markup. The conditional comment expression is
     // evaluated by Internet Explorer only. To control the rendering by other
     // browsers, use either the "downlevel-hidden" or "downlevel-revealed"
     // technique. See http://en.wikipedia.org/wiki/Conditional_comment
     // for details.
     // Ensure what we are dealing with is safe.
     // This would be done later anyway in drupal_render().
     $prefix = isset($element['#prefix']) ? $element['#prefix'] : '';
     if ($prefix && !SafeMarkup::isSafe($prefix)) {
         $prefix = Xss::filterAdmin($prefix);
     }
     $suffix = isset($element['#suffix']) ? $element['#suffix'] : '';
     if ($suffix && !SafeMarkup::isSafe($suffix)) {
         $suffix = Xss::filterAdmin($suffix);
     }
     // We ensured above that $expression is either a string we created or is
     // admin XSS filtered, and that $prefix and $suffix are also admin XSS
     // filtered if they are unsafe. Thus, all these strings are safe.
     if (!$browsers['!IE']) {
         // "downlevel-hidden".
         $element['#prefix'] = Markup::create("\n<!--[if {$expression}]>\n" . $prefix);
         $element['#suffix'] = Markup::create($suffix . "<![endif]-->\n");
     } else {
         // "downlevel-revealed".
         $element['#prefix'] = Markup::create("\n<!--[if {$expression}]><!-->\n" . $prefix);
         $element['#suffix'] = Markup::create($suffix . "<!--<![endif]-->\n");
     }
     return $element;
 }
 /**
  * Gets a rendered link from a url object.
  *
  * @param string $text
  *   The link text for the anchor tag as a translated string.
  * @param \Drupal\Core\Url|string $url
  *   The URL object or string used for the link.
  * @param array|\Drupal\Core\Template\Attribute $attributes
  *   An optional array or Attribute object of link attributes.
  *
  * @return array
  *   A render array representing a link to the given URL.
  */
 public function getLink($text, $url, $attributes = [])
 {
     if (!$url instanceof Url) {
         $url = Url::fromUri($url);
     }
     if ($attributes) {
         if ($attributes instanceof Attribute) {
             $attributes = $attributes->toArray();
         }
         if ($existing_attributes = $url->getOption('attributes')) {
             $attributes = array_merge($existing_attributes, $attributes);
         }
         $url->setOption('attributes', $attributes);
     }
     // The text has been processed by twig already, convert it to a safe object
     // for the render system.
     if ($text instanceof \Twig_Markup) {
         $text = Markup::create($text);
     }
     $build = ['#type' => 'link', '#title' => $text, '#url' => $url];
     return $build;
 }
Example #10
0
 public function providerTestReplaceEscaping()
 {
     $data = [];
     // No tokens. The first argument to Token::replace() should not be escaped.
     $data['no-tokens'] = ['muh', [], 'muh'];
     $data['html-in-string'] = ['<h1>Giraffe</h1>', [], '<h1>Giraffe</h1>'];
     $data['html-in-string-quote'] = ['<h1>Giraffe"</h1>', [], '<h1>Giraffe"</h1>'];
     $data['simple-placeholder-with-plain-text'] = ['<h1>[token:meh]</h1>', ['[token:meh]' => 'Giraffe"'], '<h1>' . Html::escape('Giraffe"') . '</h1>'];
     $data['simple-placeholder-with-safe-html'] = ['<h1>[token:meh]</h1>', ['[token:meh]' => Markup::create('<em>Emphasized</em>')], '<h1><em>Emphasized</em></h1>'];
     return $data;
 }
 /**
  * Tests setting messages and removing one before it is displayed.
  *
  * @return string
  *   Empty string, we just test the setting of messages.
  */
 public function drupalSetMessageTest()
 {
     // Set two messages.
     drupal_set_message('First message (removed).');
     drupal_set_message(t('Second message with <em>markup!</em> (not removed).'));
     // Remove the first.
     unset($_SESSION['messages']['status'][0]);
     // Duplicate message check.
     drupal_set_message('Non Duplicated message', 'status', FALSE);
     drupal_set_message('Non Duplicated message', 'status', FALSE);
     drupal_set_message('Duplicated message', 'status', TRUE);
     drupal_set_message('Duplicated message', 'status', TRUE);
     // Add a Markup message.
     drupal_set_message(Markup::create('Markup with <em>markup!</em>'));
     // Test duplicate Markup messages.
     drupal_set_message(Markup::create('Markup with <em>markup!</em>'));
     // Ensure that multiple Markup messages work.
     drupal_set_message(Markup::create('Markup2 with <em>markup!</em>'));
     // Test mixing of types.
     drupal_set_message(Markup::create('Non duplicate Markup / string.'));
     drupal_set_message('Non duplicate Markup / string.');
     drupal_set_message(Markup::create('Duplicate Markup / string.'), 'status', TRUE);
     drupal_set_message('Duplicate Markup / string.', 'status', TRUE);
     // Test auto-escape of non safe strings.
     drupal_set_message('<em>This<span>markup will be</span> escaped</em>.');
     return [];
 }
Example #12
0
 public function providerTestAttributeValues()
 {
     $data = [];
     $string = '"> <script>alert(123)</script>"';
     $data['safe-object-xss1'] = [['title' => Markup::create($string)], ' title="&quot;&gt; alert(123)&quot;"'];
     $data['non-safe-object-xss1'] = [['title' => $string], ' title="' . Html::escape($string) . '"'];
     $string = '&quot;><script>alert(123)</script>';
     $data['safe-object-xss2'] = [['title' => Markup::create($string)], ' title="&quot;&gt;alert(123)"'];
     $data['non-safe-object-xss2'] = [['title' => $string], ' title="' . Html::escape($string) . '"'];
     return $data;
 }
  /**
   * Tests the link_generator Twig functions.
   */
  public function testTwigLinkGenerator() {
    $this->drupalGet('twig-theme-test/link-generator');

    /** @var \Drupal\Core\Utility\LinkGenerator $link_generator */
    $link_generator = $this->container->get('link_generator');


    $generated_url = Url::fromRoute('user.register', [], ['absolute' => TRUE])->toString(TRUE)->getGeneratedUrl();
    $expected = [
      'link via the linkgenerator: ' . $link_generator->generate('register', new Url('user.register', [], ['absolute' => TRUE])),
      'link via the linkgenerator: ' . $link_generator->generate('register', new Url('user.register', [], ['absolute' => TRUE, 'attributes' => ['foo' => 'bar']])),
      'link via the linkgenerator: ' . $link_generator->generate('register', new Url('user.register', [], ['attributes' => ['foo' => 'bar', 'id' => 'kitten']])),
      'link via the linkgenerator: ' . $link_generator->generate('register', new Url('user.register', [], ['attributes' => ['id' => 'kitten']])),
      'link via the linkgenerator: ' . $link_generator->generate('register', new Url('user.register', [], ['attributes' => ['class' => ['llama', 'kitten', 'panda']]])),
      'link via the linkgenerator: ' . $link_generator->generate(Markup::create('<span>register</span>'), new Url('user.register', [], ['absolute' => TRUE])),
      'link via the linkgenerator: <a href="' . $generated_url . '"><span>register</span><svg></svg></a>',
    ];

    // Verify that link() has the ability to bubble cacheability metadata:
    // absolute URLs should bubble the 'url.site' cache context. (This only
    // needs to test that cacheability metadata is bubbled *at all*; detailed
    // tests for *which* cacheability metadata is bubbled live elsewhere.)
    $this->assertCacheContext('url.site');

    $content = $this->getRawContent();
    $this->assertFalse(empty($content), 'Page content is not empty');
    foreach ($expected as $string) {
      $this->assertRaw('<div>' . $string . '</div>');
    }
  }
 function sectionRender($section)
 {
     if (empty($section->regions) || $section->key == 'unassigned') {
         return '';
     }
     $content = '';
     foreach ($section->regions as $region) {
         if ($region_content = $this->regionRender($region)) {
             $content .= $region_content;
         }
     }
     if ($content) {
         $render_array = ['#theme' => 'inv-section', '#content' => ['#markup' => Markup::create($content)], '#section' => $section, '#attributes' => ['id' => Html::getId('section-' . $section->key), 'class' => [Html::getClass('inv-section')]]];
         return drupal_render($render_array);
     }
     return "";
 }
Example #15
0
 /**
  * Sends no-JS BigPipe placeholders' replacements as embedded HTML responses.
  *
  * @param string $html
  *   HTML markup.
  * @param array $no_js_placeholders
  *   Associative array; the no-JS BigPipe placeholders. Keys are the BigPipe
  *   selectors.
  * @param \Drupal\Core\Asset\AttachedAssetsInterface $cumulative_assets
  *   The cumulative assets sent so far; to be updated while rendering no-JS
  *   BigPipe placeholders.
  */
 protected function sendNoJsPlaceholders($html, $no_js_placeholders, AttachedAssetsInterface $cumulative_assets)
 {
     // Split the HTML on every no-JS placeholder string.
     $prepare_for_preg_split = function ($placeholder_string) {
         return '(' . preg_quote($placeholder_string, '/') . ')';
     };
     $preg_placeholder_strings = array_map($prepare_for_preg_split, array_keys($no_js_placeholders));
     $fragments = preg_split('/' . implode('|', $preg_placeholder_strings) . '/', $html, NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
     foreach ($fragments as $fragment) {
         // If the fragment isn't one of the no-JS placeholders, it is the HTML in
         // between placeholders and it must be printed & flushed immediately. The
         // rest of the logic in the loop handles the placeholders.
         if (!isset($no_js_placeholders[$fragment])) {
             print $fragment;
             flush();
             continue;
         }
         $placeholder = $fragment;
         assert('isset($no_js_placeholders[$placeholder])');
         $token = Crypt::randomBytesBase64(55);
         // Render the placeholder, but include the cumulative settings assets, so
         // we can calculate the overall settings for the entire page.
         $placeholder_plus_cumulative_settings = ['placeholder' => $no_js_placeholders[$placeholder], 'cumulative_settings_' . $token => ['#attached' => ['drupalSettings' => $cumulative_assets->getSettings()]]];
         $elements = $this->renderPlaceholder($placeholder, $placeholder_plus_cumulative_settings);
         // Create a new HtmlResponse. Ensure the CSS and (non-bottom) JS is sent
         // before the HTML they're associated with. In other words: ensure the
         // critical assets for this placeholder's markup are loaded first.
         // @see \Drupal\Core\Render\HtmlResponseSubscriber
         // @see template_preprocess_html()
         $css_placeholder = '<nojs-bigpipe-placeholder-styles-placeholder token="' . $token . '">';
         $js_placeholder = '<nojs-bigpipe-placeholder-scripts-placeholder token="' . $token . '">';
         $elements['#markup'] = Markup::create($css_placeholder . $js_placeholder . (string) $elements['#markup']);
         $elements['#attached']['html_response_attachment_placeholders']['styles'] = $css_placeholder;
         $elements['#attached']['html_response_attachment_placeholders']['scripts'] = $js_placeholder;
         $html_response = new HtmlResponse();
         $html_response->setContent($elements);
         $html_response->getCacheableMetadata()->setCacheMaxAge(0);
         // Push a fake request with the asset libraries loaded so far and dispatch
         // KernelEvents::RESPONSE event. This results in the attachments for the
         // HTML response being processed by HtmlResponseAttachmentsProcessor and
         // hence:
         // - the HTML to load the CSS can be rendered.
         // - the HTML to load the JS (at the top) can be rendered.
         $fake_request = $this->requestStack->getMasterRequest()->duplicate();
         $fake_request->request->set('ajax_page_state', ['libraries' => implode(',', $cumulative_assets->getAlreadyLoadedLibraries())]);
         $this->requestStack->push($fake_request);
         $event = new FilterResponseEvent($this->httpKernel, $fake_request, HttpKernelInterface::SUB_REQUEST, $html_response);
         $this->eventDispatcher->dispatch(KernelEvents::RESPONSE, $event);
         $html_response = $event->getResponse();
         $this->requestStack->pop();
         // Send this embedded HTML response.
         print $html_response->getContent();
         flush();
         // Another placeholder was rendered and sent, track the set of asset
         // libraries sent so far. Any new settings also need to be tracked, so
         // they can be sent in ::sendPreBody().
         // @todo What if drupalSettings already was printed in the HTML <head>? That case is not yet handled. In that case, no-JS BigPipe would cause broken (incomplete) drupalSettings… This would not matter if it were only used if JS is not enabled, but that's not the only use case. However, this
         $cumulative_assets->setAlreadyLoadedLibraries(array_merge($cumulative_assets->getAlreadyLoadedLibraries(), $html_response->getAttachments()['library']));
         $cumulative_assets->setSettings($html_response->getAttachments()['drupalSettings']);
     }
 }
Example #16
0
 /**
  * Test tokens on node with the token view mode overriding default formatters.
  */
 public function testTokenViewMode()
 {
     $value = 'A really long string that should be trimmed by the special formatter on token view we are going to have.';
     // The formatter we are going to use will eventually call Unicode::strlen.
     // This expects that the Unicode has already been explicitly checked, which
     // happens in DrupalKernel. But since that doesn't run in kernel tests, we
     // explicitly call this here.
     Unicode::check();
     // Create a node with a value in the text field and test its token.
     $entity = Node::create(['title' => 'Test node title', 'type' => 'article', 'test_field' => ['value' => $value, 'format' => $this->testFormat->id()]]);
     $entity->save();
     $this->assertTokens('node', ['node' => $entity], ['test_field' => Markup::create($value)]);
     // Now, create a token view mode which sets a different format for
     // test_field. When replacing tokens, this formatter should be picked over
     // the default formatter for the field type.
     // @see field_tokens().
     $view_mode = EntityViewMode::create(['id' => 'node.token', 'targetEntityType' => 'node']);
     $view_mode->save();
     $entity_display = entity_get_display('node', 'article', 'token');
     $entity_display->setComponent('test_field', ['type' => 'text_trimmed', 'settings' => ['trim_length' => 50]]);
     $entity_display->save();
     $this->assertTokens('node', ['node' => $entity], ['test_field' => Markup::create(substr($value, 0, 50))]);
 }
Example #17
0
 /**
  * {@inheritdoc}
  */
 public function getSettingsLink()
 {
     $url = URL::fromRoute('ckeditor_media_embed.ckeditor_media_embed_settings_form', array('destination' => \Drupal::service('path.current')->getPath()));
     return Markup::create(\Drupal::l($this->t('CKEditor Media Embed plugin settings page'), $url));
 }
Example #18
0
 /**
  * {@inheritdoc}
  */
 public function render($hook, array $variables)
 {
     static $default_attributes;
     $active_theme = $this->getActiveTheme();
     // If called before all modules are loaded, we do not necessarily have a full
     // theme registry to work with, and therefore cannot process the theme
     // request properly. See also \Drupal\Core\Theme\Registry::get().
     if (!$this->moduleHandler->isLoaded() && !defined('MAINTENANCE_MODE')) {
         throw new \Exception(t('_theme() may not be called until all modules are loaded.'));
     }
     $theme_registry = $this->themeRegistry->getRuntime();
     // If an array of hook candidates were passed, use the first one that has an
     // implementation.
     if (is_array($hook)) {
         foreach ($hook as $candidate) {
             if ($theme_registry->has($candidate)) {
                 break;
             }
         }
         $hook = $candidate;
     }
     // Save the original theme hook, so it can be supplied to theme variable
     // preprocess callbacks.
     $original_hook = $hook;
     // If there's no implementation, check for more generic fallbacks.
     // If there's still no implementation, log an error and return an empty
     // string.
     if (!$theme_registry->has($hook)) {
         // Iteratively strip everything after the last '__' delimiter, until an
         // implementation is found.
         while ($pos = strrpos($hook, '__')) {
             $hook = substr($hook, 0, $pos);
             if ($theme_registry->has($hook)) {
                 break;
             }
         }
         if (!$theme_registry->has($hook)) {
             // Only log a message when not trying theme suggestions ($hook being an
             // array).
             if (!isset($candidate)) {
                 \Drupal::logger('theme')->warning('Theme hook %hook not found.', array('%hook' => $hook));
             }
             // There is no theme implementation for the hook passed. Return FALSE so
             // the function calling _theme() can differentiate between a hook that
             // exists and renders an empty string and a hook that is not
             // implemented.
             return FALSE;
         }
     }
     $info = $theme_registry->get($hook);
     // If a renderable array is passed as $variables, then set $variables to
     // the arguments expected by the theme function.
     if (isset($variables['#theme']) || isset($variables['#theme_wrappers'])) {
         $element = $variables;
         $variables = array();
         if (isset($info['variables'])) {
             foreach (array_keys($info['variables']) as $name) {
                 if (isset($element["#{$name}"]) || array_key_exists("#{$name}", $element)) {
                     $variables[$name] = $element["#{$name}"];
                 }
             }
         } else {
             $variables[$info['render element']] = $element;
             // Give a hint to render engines to prevent infinite recursion.
             $variables[$info['render element']]['#render_children'] = TRUE;
         }
     }
     // Merge in argument defaults.
     if (!empty($info['variables'])) {
         $variables += $info['variables'];
     } elseif (!empty($info['render element'])) {
         $variables += array($info['render element'] => array());
     }
     // Supply original caller info.
     $variables += array('theme_hook_original' => $original_hook);
     // Set base hook for later use. For example if '#theme' => 'node__article'
     // is called, we run hook_theme_suggestions_node_alter() rather than
     // hook_theme_suggestions_node__article_alter(), and also pass in the base
     // hook as the last parameter to the suggestions alter hooks.
     if (isset($info['base hook'])) {
         $base_theme_hook = $info['base hook'];
     } else {
         $base_theme_hook = $hook;
     }
     // Invoke hook_theme_suggestions_HOOK().
     $suggestions = $this->moduleHandler->invokeAll('theme_suggestions_' . $base_theme_hook, array($variables));
     // If _theme() was invoked with a direct theme suggestion like
     // '#theme' => 'node__article', add it to the suggestions array before
     // invoking suggestion alter hooks.
     if (isset($info['base hook'])) {
         $suggestions[] = $hook;
     }
     // Invoke hook_theme_suggestions_alter() and
     // hook_theme_suggestions_HOOK_alter().
     $hooks = array('theme_suggestions', 'theme_suggestions_' . $base_theme_hook);
     $this->moduleHandler->alter($hooks, $suggestions, $variables, $base_theme_hook);
     $this->alter($hooks, $suggestions, $variables, $base_theme_hook);
     // Check if each suggestion exists in the theme registry, and if so,
     // use it instead of the hook that _theme() was called with. For example, a
     // function may call _theme('node', ...), but a module can add
     // 'node__article' as a suggestion via hook_theme_suggestions_HOOK_alter(),
     // enabling a theme to have an alternate template file for article nodes.
     foreach (array_reverse($suggestions) as $suggestion) {
         if ($theme_registry->has($suggestion)) {
             $info = $theme_registry->get($suggestion);
             break;
         }
     }
     // Include a file if the theme function or variable preprocessor is held
     // elsewhere.
     if (!empty($info['includes'])) {
         foreach ($info['includes'] as $include_file) {
             include_once $this->root . '/' . $include_file;
         }
     }
     // Invoke the variable preprocessors, if any.
     if (isset($info['base hook'])) {
         $base_hook = $info['base hook'];
         $base_hook_info = $theme_registry->get($base_hook);
         // Include files required by the base hook, since its variable
         // preprocessors might reside there.
         if (!empty($base_hook_info['includes'])) {
             foreach ($base_hook_info['includes'] as $include_file) {
                 include_once $this->root . '/' . $include_file;
             }
         }
         if (isset($base_hook_info['preprocess functions'])) {
             // Set a variable for the 'theme_hook_suggestion'. This is used to
             // maintain backwards compatibility with template engines.
             $theme_hook_suggestion = $hook;
         }
     }
     if (isset($info['preprocess functions'])) {
         foreach ($info['preprocess functions'] as $preprocessor_function) {
             if (function_exists($preprocessor_function)) {
                 $preprocessor_function($variables, $hook, $info);
             }
         }
         // Allow theme preprocess functions to set $variables['#attached'] and
         // $variables['#cache'] and use them like the corresponding element
         // properties on render arrays. In Drupal 8, this is the (only) officially
         // supported method of attaching bubbleable metadata from preprocess
         // functions. Assets attached here should be associated with the template
         // that we are preprocessing variables for.
         $preprocess_bubbleable = [];
         foreach (['#attached', '#cache'] as $key) {
             if (isset($variables[$key])) {
                 $preprocess_bubbleable[$key] = $variables[$key];
             }
         }
         // We do not allow preprocess functions to define cacheable elements.
         unset($preprocess_bubbleable['#cache']['keys']);
         if ($preprocess_bubbleable) {
             // @todo Inject the Renderer in https://www.drupal.org/node/2529438.
             drupal_render($preprocess_bubbleable);
         }
     }
     // Generate the output using either a function or a template.
     $output = '';
     if (isset($info['function'])) {
         if (function_exists($info['function'])) {
             // Theme functions do not render via the theme engine, so the output is
             // not autoescaped. However, we can only presume that the theme function
             // has been written correctly and that the markup is safe.
             $output = Markup::create($info['function']($variables));
         }
     } else {
         $render_function = 'twig_render_template';
         $extension = '.html.twig';
         // The theme engine may use a different extension and a different
         // renderer.
         $theme_engine = $active_theme->getEngine();
         if (isset($theme_engine)) {
             if ($info['type'] != 'module') {
                 if (function_exists($theme_engine . '_render_template')) {
                     $render_function = $theme_engine . '_render_template';
                 }
                 $extension_function = $theme_engine . '_extension';
                 if (function_exists($extension_function)) {
                     $extension = $extension_function();
                 }
             }
         }
         // In some cases, a template implementation may not have had
         // template_preprocess() run (for example, if the default implementation
         // is a function, but a template overrides that default implementation).
         // In these cases, a template should still be able to expect to have
         // access to the variables provided by template_preprocess(), so we add
         // them here if they don't already exist. We don't want the overhead of
         // running template_preprocess() twice, so we use the 'directory' variable
         // to determine if it has already run, which while not completely
         // intuitive, is reasonably safe, and allows us to save on the overhead of
         // adding some new variable to track that.
         if (!isset($variables['directory'])) {
             $default_template_variables = array();
             template_preprocess($default_template_variables, $hook, $info);
             $variables += $default_template_variables;
         }
         if (!isset($default_attributes)) {
             $default_attributes = new Attribute();
         }
         foreach (array('attributes', 'title_attributes', 'content_attributes') as $key) {
             if (isset($variables[$key]) && !$variables[$key] instanceof Attribute) {
                 if ($variables[$key]) {
                     $variables[$key] = new Attribute($variables[$key]);
                 } else {
                     // Create empty attributes.
                     $variables[$key] = clone $default_attributes;
                 }
             }
         }
         // Render the output using the template file.
         $template_file = $info['template'] . $extension;
         if (isset($info['path'])) {
             $template_file = $info['path'] . '/' . $template_file;
         }
         // Add the theme suggestions to the variables array just before rendering
         // the template for backwards compatibility with template engines.
         $variables['theme_hook_suggestions'] = $suggestions;
         // For backwards compatibility, pass 'theme_hook_suggestion' on to the
         // template engine. This is only set when calling a direct suggestion like
         // '#theme' => 'menu__shortcut_default' when the template exists in the
         // current theme.
         if (isset($theme_hook_suggestion)) {
             $variables['theme_hook_suggestion'] = $theme_hook_suggestion;
         }
         $output = $render_function($template_file, $variables);
     }
     return $output instanceof MarkupInterface ? $output : (string) $output;
 }
Example #19
0
 /**
  * Tests the link method with html.
  *
  * @see \Drupal\Core\Utility\LinkGenerator::generate()
  */
 public function testGenerateWithHtml()
 {
     $this->urlGenerator->expects($this->at(0))->method('generateFromRoute')->with('test_route_5', array(), $this->defaultOptions)->willReturn((new GeneratedUrl())->setGeneratedUrl('/test-route-5'));
     $this->urlGenerator->expects($this->at(1))->method('generateFromRoute')->with('test_route_5', array(), $this->defaultOptions)->willReturn((new GeneratedUrl())->setGeneratedUrl('/test-route-5'));
     // Test that HTML tags are stripped from the 'title' attribute.
     $url = new Url('test_route_5', array(), array('attributes' => array('title' => '<em>HTML Tooltip</em>')));
     $url->setUrlGenerator($this->urlGenerator);
     $result = $this->linkGenerator->generate('Test', $url);
     $this->assertLink(array('attributes' => array('href' => '/test-route-5', 'title' => 'HTML Tooltip')), $result);
     // Test that safe HTML is output inside the anchor tag unescaped. The
     // Markup::create() call is an intentional unit test for the interaction
     // between MarkupInterface and the LinkGenerator.
     $url = new Url('test_route_5', array());
     $url->setUrlGenerator($this->urlGenerator);
     $result = $this->linkGenerator->generate(Markup::create('<em>HTML output</em>'), $url);
     $this->assertLink(array('attributes' => array('href' => '/test-route-5'), 'child' => array('tag' => 'em')), $result);
     $this->assertTrue(strpos($result, '<em>HTML output</em>') !== FALSE);
 }
Example #20
0
 /**
  * Renders a twig string directly.
  *
  * Warning: You should use the render element 'inline_template' together with
  * the #template attribute instead of this method directly.
  * On top of that you have to ensure that the template string is not dynamic
  * but just an ordinary static php string, because there may be installations
  * using read-only PHPStorage that want to generate all possible twig
  * templates as part of a build step. So it is important that an automated
  * script can find the templates and extract them. This is only possible if
  * the template is a regular string.
  *
  * @param string $template_string
  *   The template string to render with placeholders.
  * @param array $context
  *   An array of parameters to pass to the template.
  *
  * @return \Drupal\Component\Render\MarkupInterface|string
  *   The rendered inline template as a Markup object.
  *
  * @see \Drupal\Core\Template\Loader\StringLoader::exists()
  */
 public function renderInline($template_string, array $context = array())
 {
     // Prefix all inline templates with a special comment.
     $template_string = '{# inline_template_start #}' . $template_string;
     return Markup::create($this->loadTemplate($template_string, NULL)->render($context));
 }
 /**
  * Provide test examples.
  */
 public function providerTestMarkupInterfaceEmpty()
 {
     return ['empty TranslatableMarkup' => ['', new TranslatableMarkup('')], 'non-empty TranslatableMarkup' => ['<span>test</span>', new TranslatableMarkup('test')], 'empty FormattableMarkup' => ['', new FormattableMarkup('', ['@foo' => 'bar'])], 'non-empty FormattableMarkup' => ['<span>bar</span>', new FormattableMarkup('@foo', ['@foo' => 'bar'])], 'non-empty Markup' => ['<span>test</span>', Markup::create('test')], 'empty GeneratedLink' => ['', new GeneratedLink()], 'non-empty GeneratedLink' => ['<span><a hef="http://www.example.com">test</a></span>', (new GeneratedLink())->setGeneratedLink('<a hef="http://www.example.com">test</a>')], 'empty SafeMarkupTestMarkup' => ['<span></span>', SafeMarkupTestMarkup::create('')], 'non-empty SafeMarkupTestMarkup' => ['<span>test</span>', SafeMarkupTestMarkup::create('test')]];
 }
 /**
  * Provide test examples.
  */
 public function providerTestThemeRenderAndAutoescape()
 {
     return ['empty string unchanged' => ['', ''], 'simple string unchanged' => ['ab', 'ab'], 'int (scalar) cast to string' => [111, '111'], 'float (scalar) cast to string' => [2.1, '2.10'], '> is escaped' => ['>', '&gt;'], 'Markup EM tag is unchanged' => [Markup::create('<em>hi</em>'), '<em>hi</em>'], 'Markup SCRIPT tag is unchanged' => [Markup::create('<script>alert("hi");</script>'), '<script>alert("hi");</script>'], 'EM tag in string is escaped' => ['<em>hi</em>', Html::escape('<em>hi</em>')], 'type link render array is rendered' => [['#type' => 'link', '#title' => 'Text', '#url' => '<none>'], '<a href="">Text</a>'], 'type markup with EM tags is rendered' => [['#markup' => '<em>hi</em>'], '<em>hi</em>'], 'SCRIPT tag in string is escaped' => ['<script>alert(123)</script>', Html::escape('<script>alert(123)</script>')], 'type plain_text render array EM tag is escaped' => [['#plain_text' => '<em>hi</em>'], Html::escape('<em>hi</em>')], 'type hidden render array is rendered' => [['#type' => 'hidden', '#name' => 'foo', '#value' => 'bar'], "<input type=\"hidden\" name=\"foo\" value=\"bar\" />\n"]];
 }
 /**
  * Provides the two classes of placeholders: cacheable and uncacheable.
  *
  * i.e. with or without #cache[keys].
  *
  * Also, different types:
  * - A) automatically generated placeholder
  *   - 1) manually triggered (#create_placeholder = TRUE)
  *   - 2) automatically triggered (based on max-age = 0 at the top level)
  *   - 3) automatically triggered (based on high cardinality cache contexts at
  *        the top level)
  *   - 4) automatically triggered (based on high-invalidation frequency cache
  *        tags at the top level)
  *   - 5) automatically triggered (based on max-age = 0 in its subtree, i.e.
  *        via bubbling)
  *   - 6) automatically triggered (based on high cardinality cache contexts in
  *        its subtree, i.e. via bubbling)
  *   - 7) automatically triggered (based on high-invalidation frequency cache
  *        tags in its subtree, i.e. via bubbling)
  * - B) manually generated placeholder
  *
  * So, in total 2*8 = 16 permutations. (On one axis: uncacheable vs.
  * uncacheable = 2; on the other axis: A1–7 and B = 8.)
  *
  * @todo Case A5 is not yet supported by core. So that makes for only 14
  *   permutations currently, instead of 16. That will be done in
  *   https://www.drupal.org/node/2559847
  *
  * @return array
  */
 public function providerPlaceholders()
 {
     $args = [$this->randomContextValue()];
     $generate_placeholder_markup = function ($cache_keys = NULL) use($args) {
         $token_render_array = ['#lazy_builder' => ['Drupal\\Tests\\Core\\Render\\PlaceholdersTest::callback', $args]];
         if (is_array($cache_keys)) {
             $token_render_array['#cache']['keys'] = $cache_keys;
         }
         $token = hash('crc32b', serialize($token_render_array));
         // \Drupal\Core\Render\Markup::create() is necessary as the render
         // system would mangle this markup. As this is exactly what happens at
         // runtime this is a valid use-case.
         return Markup::create('<drupal-render-placeholder callback="Drupal\\Tests\\Core\\Render\\PlaceholdersTest::callback" arguments="' . '0=' . $args[0] . '" token="' . $token . '"></drupal-render-placeholder>');
     };
     $extract_placeholder_render_array = function ($placeholder_render_array) {
         return array_intersect_key($placeholder_render_array, ['#lazy_builder' => TRUE, '#cache' => TRUE]);
     };
     // Note the presence of '#create_placeholder'.
     $base_element_a1 = ['#attached' => ['drupalSettings' => ['foo' => 'bar']], 'placeholder' => ['#cache' => ['contexts' => []], '#create_placeholder' => TRUE, '#lazy_builder' => ['Drupal\\Tests\\Core\\Render\\PlaceholdersTest::callback', $args]]];
     // Note the absence of '#create_placeholder', presence of max-age=0 at the
     // top level.
     $base_element_a2 = ['#attached' => ['drupalSettings' => ['foo' => 'bar']], 'placeholder' => ['#cache' => ['contexts' => [], 'max-age' => 0], '#lazy_builder' => ['Drupal\\Tests\\Core\\Render\\PlaceholdersTest::callback', $args]]];
     // Note the absence of '#create_placeholder', presence of high cardinality
     // cache context at the top level.
     $base_element_a3 = ['#attached' => ['drupalSettings' => ['foo' => 'bar']], 'placeholder' => ['#cache' => ['contexts' => ['user']], '#lazy_builder' => ['Drupal\\Tests\\Core\\Render\\PlaceholdersTest::callback', $args]]];
     // Note the absence of '#create_placeholder', presence of high-invalidation
     // frequency cache tag at the top level.
     $base_element_a4 = ['#attached' => ['drupalSettings' => ['foo' => 'bar']], 'placeholder' => ['#cache' => ['contexts' => [], 'tags' => ['current-temperature']], '#lazy_builder' => ['Drupal\\Tests\\Core\\Render\\PlaceholdersTest::callback', $args]]];
     // Note the absence of '#create_placeholder', presence of max-age=0 created
     // by the #lazy_builder callback.
     // @todo in https://www.drupal.org/node/2559847
     $base_element_a5 = [];
     // Note the absence of '#create_placeholder', presence of high cardinality
     // cache context created by the #lazy_builder callback.
     // @see \Drupal\Tests\Core\Render\PlaceholdersTest::callbackPerUser()
     $base_element_a6 = ['#attached' => ['drupalSettings' => ['foo' => 'bar']], 'placeholder' => ['#cache' => ['contexts' => []], '#lazy_builder' => ['Drupal\\Tests\\Core\\Render\\PlaceholdersTest::callbackPerUser', $args]]];
     // Note the absence of '#create_placeholder', presence of high-invalidation
     // frequency cache tag created by the #lazy_builder callback.
     // @see \Drupal\Tests\Core\Render\PlaceholdersTest::callbackTagCurrentTemperature()
     $base_element_a7 = ['#attached' => ['drupalSettings' => ['foo' => 'bar']], 'placeholder' => ['#cache' => ['contexts' => []], '#lazy_builder' => ['Drupal\\Tests\\Core\\Render\\PlaceholdersTest::callbackTagCurrentTemperature', $args]]];
     // Note the absence of '#create_placeholder', but the presence of
     // '#attached[placeholders]'.
     $base_element_b = ['#markup' => $generate_placeholder_markup(), '#attached' => ['drupalSettings' => ['foo' => 'bar'], 'placeholders' => [(string) $generate_placeholder_markup() => ['#lazy_builder' => ['Drupal\\Tests\\Core\\Render\\PlaceholdersTest::callback', $args]]]]];
     $keys = ['placeholder', 'output', 'can', 'be', 'render', 'cached', 'too'];
     $cases = [];
     // Case one: render array that has a placeholder that is:
     // - automatically created, but manually triggered (#create_placeholder = TRUE)
     // - uncacheable
     $element_without_cache_keys = $base_element_a1;
     $expected_placeholder_render_array = $extract_placeholder_render_array($base_element_a1['placeholder']);
     $cases[] = [$element_without_cache_keys, $args, $expected_placeholder_render_array, FALSE, [], [], []];
     // Case two: render array that has a placeholder that is:
     // - automatically created, but manually triggered (#create_placeholder = TRUE)
     // - cacheable
     $element_with_cache_keys = $base_element_a1;
     $element_with_cache_keys['placeholder']['#cache']['keys'] = $keys;
     $expected_placeholder_render_array['#cache']['keys'] = $keys;
     $cases[] = [$element_with_cache_keys, $args, $expected_placeholder_render_array, $keys, [], [], ['#markup' => '<p>This is a rendered placeholder!</p>', '#attached' => ['drupalSettings' => ['dynamic_animal' => $args[0]]], '#cache' => ['contexts' => [], 'tags' => [], 'max-age' => Cache::PERMANENT]]];
     // Case three: render array that has a placeholder that is:
     // - automatically created, and automatically triggered due to max-age=0
     // - uncacheable
     $element_without_cache_keys = $base_element_a2;
     $expected_placeholder_render_array = $extract_placeholder_render_array($base_element_a2['placeholder']);
     $cases[] = [$element_without_cache_keys, $args, $expected_placeholder_render_array, FALSE, [], [], []];
     // Case four: render array that has a placeholder that is:
     // - automatically created, but automatically triggered due to max-age=0
     // - cacheable
     $element_with_cache_keys = $base_element_a2;
     $element_with_cache_keys['placeholder']['#cache']['keys'] = $keys;
     $expected_placeholder_render_array['#cache']['keys'] = $keys;
     $cases[] = [$element_with_cache_keys, $args, $expected_placeholder_render_array, FALSE, [], [], []];
     // Case five: render array that has a placeholder that is:
     // - automatically created, and automatically triggered due to high
     //   cardinality cache contexts
     // - uncacheable
     $element_without_cache_keys = $base_element_a3;
     $expected_placeholder_render_array = $extract_placeholder_render_array($base_element_a3['placeholder']);
     $cases[] = [$element_without_cache_keys, $args, $expected_placeholder_render_array, FALSE, [], [], []];
     // Case six: render array that has a placeholder that is:
     // - automatically created, and automatically triggered due to high
     //   cardinality cache contexts
     // - cacheable
     $element_with_cache_keys = $base_element_a3;
     $element_with_cache_keys['placeholder']['#cache']['keys'] = $keys;
     $expected_placeholder_render_array['#cache']['keys'] = $keys;
     // The CID parts here consist of the cache keys plus the 'user' cache
     // context, which in this unit test is simply the given cache context token,
     // see \Drupal\Tests\Core\Render\RendererTestBase::setUp().
     $cid_parts = array_merge($keys, ['user']);
     $cases[] = [$element_with_cache_keys, $args, $expected_placeholder_render_array, $cid_parts, [], [], ['#markup' => '<p>This is a rendered placeholder!</p>', '#attached' => ['drupalSettings' => ['dynamic_animal' => $args[0]]], '#cache' => ['contexts' => ['user'], 'tags' => [], 'max-age' => Cache::PERMANENT]]];
     // Case seven: render array that has a placeholder that is:
     // - automatically created, and automatically triggered due to high
     //   invalidation frequency cache tags
     // - uncacheable
     $element_without_cache_keys = $base_element_a4;
     $expected_placeholder_render_array = $extract_placeholder_render_array($base_element_a4['placeholder']);
     $cases[] = [$element_without_cache_keys, $args, $expected_placeholder_render_array, FALSE, [], [], []];
     // Case eight: render array that has a placeholder that is:
     // - automatically created, and automatically triggered due to high
     //   invalidation frequency cache tags
     // - cacheable
     $element_with_cache_keys = $base_element_a4;
     $element_with_cache_keys['placeholder']['#cache']['keys'] = $keys;
     $expected_placeholder_render_array['#cache']['keys'] = $keys;
     $cases[] = [$element_with_cache_keys, $args, $expected_placeholder_render_array, $keys, [], [], ['#markup' => '<p>This is a rendered placeholder!</p>', '#attached' => ['drupalSettings' => ['dynamic_animal' => $args[0]]], '#cache' => ['contexts' => [], 'tags' => ['current-temperature'], 'max-age' => Cache::PERMANENT]]];
     // Case nine: render array that DOES NOT have a placeholder that is:
     // - NOT created, despite max-age=0 that is bubbled
     // - uncacheable
     // (because the render element with #lazy_builder does not have #cache[keys]
     // and hence the max-age=0 bubbles up further)
     // @todo in https://www.drupal.org/node/2559847
     // Case ten: render array that has a placeholder that is:
     // - automatically created, and automatically triggered due to max-age=0
     //   that is bubbled
     // - cacheable
     // @todo in https://www.drupal.org/node/2559847
     // Case eleven: render array that DOES NOT have a placeholder that is:
     // - NOT created, despite high cardinality cache contexts that are bubbled
     // - uncacheable
     $element_without_cache_keys = $base_element_a6;
     $expected_placeholder_render_array = $extract_placeholder_render_array($base_element_a6['placeholder']);
     $cases[] = [$element_without_cache_keys, $args, $expected_placeholder_render_array, FALSE, ['user'], [], []];
     // Case twelve: render array that has a placeholder that is:
     // - automatically created, and automatically triggered due to high
     //   cardinality cache contexts that are bubbled
     // - cacheable
     $element_with_cache_keys = $base_element_a6;
     $element_with_cache_keys['placeholder']['#cache']['keys'] = $keys;
     $expected_placeholder_render_array['#cache']['keys'] = $keys;
     $cases[] = [$element_with_cache_keys, $args, $expected_placeholder_render_array, $keys, ['user'], [], ['#markup' => '<p>This is a rendered placeholder!</p>', '#attached' => ['drupalSettings' => ['dynamic_animal' => $args[0]]], '#cache' => ['contexts' => ['user'], 'tags' => [], 'max-age' => Cache::PERMANENT]]];
     // Case thirteen: render array that has a placeholder that is:
     // - automatically created, and automatically triggered due to high
     //   invalidation frequency cache tags that are bubbled
     // - uncacheable
     $element_without_cache_keys = $base_element_a7;
     $expected_placeholder_render_array = $extract_placeholder_render_array($base_element_a7['placeholder']);
     $cases[] = [$element_without_cache_keys, $args, $expected_placeholder_render_array, FALSE, [], ['current-temperature'], []];
     // Case fourteen: render array that has a placeholder that is:
     // - automatically created, and automatically triggered due to high
     //   invalidation frequency cache tags that are bubbled
     // - cacheable
     $element_with_cache_keys = $base_element_a7;
     $element_with_cache_keys['placeholder']['#cache']['keys'] = $keys;
     $expected_placeholder_render_array['#cache']['keys'] = $keys;
     $cases[] = [$element_with_cache_keys, $args, $expected_placeholder_render_array, $keys, [], [], ['#markup' => '<p>This is a rendered placeholder!</p>', '#attached' => ['drupalSettings' => ['dynamic_animal' => $args[0]]], '#cache' => ['contexts' => [], 'tags' => ['current-temperature'], 'max-age' => Cache::PERMANENT]]];
     // Case fifteen: render array that has a placeholder that is:
     // - manually created
     // - uncacheable
     $x = $base_element_b;
     $expected_placeholder_render_array = $x['#attached']['placeholders'][(string) $generate_placeholder_markup()];
     unset($x['#attached']['placeholders'][(string) $generate_placeholder_markup()]['#cache']);
     $cases[] = [$x, $args, $expected_placeholder_render_array, FALSE, [], [], []];
     // Case sixteen: render array that has a placeholder that is:
     // - manually created
     // - cacheable
     $x = $base_element_b;
     $x['#markup'] = $placeholder_markup = $generate_placeholder_markup($keys);
     $placeholder_markup = (string) $placeholder_markup;
     $x['#attached']['placeholders'] = [$placeholder_markup => ['#lazy_builder' => ['Drupal\\Tests\\Core\\Render\\PlaceholdersTest::callback', $args], '#cache' => ['keys' => $keys]]];
     $expected_placeholder_render_array = $x['#attached']['placeholders'][$placeholder_markup];
     $cases[] = [$x, $args, $expected_placeholder_render_array, $keys, [], [], ['#markup' => '<p>This is a rendered placeholder!</p>', '#attached' => ['drupalSettings' => ['dynamic_animal' => $args[0]]], '#cache' => ['contexts' => [], 'tags' => [], 'max-age' => Cache::PERMANENT]]];
     return $cases;
 }
 /**
  * #lazy_builder callback; says "hello" or "yarhar".
  *
  * @return array
  */
 public static function helloOrYarhar()
 {
     return ['#markup' => Markup::create('<marquee>Yarhar llamas forever!</marquee>'), '#cache' => ['max-age' => 0]];
 }
 public function providerCastSafeStrings()
 {
     $safe_string = Markup::create('test safe string');
     return [['test simple string', 'test simple string'], [['test simple array', 'test simple array'], ['test simple array', 'test simple array']], ['test safe string', $safe_string], [['test safe string', 'test safe string'], [$safe_string, $safe_string]], [['test safe string', 'mixed array', 'test safe string'], [$safe_string, 'mixed array', $safe_string]]];
 }
Example #26
0
 /**
  * Sends no-JS BigPipe placeholders' replacements as embedded HTML responses.
  *
  * @param string $html
  *   HTML markup.
  * @param array $no_js_placeholders
  *   Associative array; the no-JS BigPipe placeholders. Keys are the BigPipe
  *   selectors.
  * @param \Drupal\Core\Asset\AttachedAssetsInterface $cumulative_assets
  *   The cumulative assets sent so far; to be updated while rendering no-JS
  *   BigPipe placeholders.
  */
 protected function sendNoJsPlaceholders($html, $no_js_placeholders, AttachedAssetsInterface $cumulative_assets)
 {
     $fragments = explode('<div data-big-pipe-selector-nojs="', $html);
     print array_shift($fragments);
     ob_end_flush();
     flush();
     foreach ($fragments as $fragment) {
         $t = explode('"></div>', $fragment, 2);
         $placeholder = $t[0];
         if (!isset($no_js_placeholders[$placeholder])) {
             continue;
         }
         $token = Crypt::randomBytesBase64(55);
         // Render the placeholder, but include the cumulative settings assets, so
         // we can calculate the overall settings for the entire page.
         $placeholder_plus_cumulative_settings = ['placeholder' => $no_js_placeholders[$placeholder], 'cumulative_settings_' . $token => ['#attached' => ['drupalSettings' => $cumulative_assets->getSettings()]]];
         $elements = $this->renderPlaceholder($placeholder, $placeholder_plus_cumulative_settings);
         // Create a new HtmlResponse. Ensure the CSS and (non-bottom) JS is sent
         // before the HTML they're associated with. In other words: ensure the
         // critical assets for this placeholder's markup are loaded first.
         // @see \Drupal\Core\Render\HtmlResponseSubscriber
         // @see template_preprocess_html()
         $css_placeholder = '<nojs-bigpipe-placeholder-styles-placeholder token="' . $token . '">';
         $js_placeholder = '<nojs-bigpipe-placeholder-scripts-placeholder token="' . $token . '">';
         $elements['#markup'] = Markup::create($css_placeholder . $js_placeholder . (string) $elements['#markup']);
         $elements['#attached']['html_response_attachment_placeholders']['styles'] = $css_placeholder;
         $elements['#attached']['html_response_attachment_placeholders']['scripts'] = $js_placeholder;
         $html_response = new HtmlResponse();
         $html_response->setContent($elements);
         $html_response->getCacheableMetadata()->setCacheMaxAge(0);
         // Push a fake request with the asset libraries loaded so far and dispatch
         // KernelEvents::RESPONSE event. This results in the attachments for the
         // HTML response being processed by HtmlResponseAttachmentsProcessor and
         // hence:
         // - the HTML to load the CSS can be rendered.
         // - the HTML to load the JS (at the top) can be rendered.
         $fake_request = $this->requestStack->getMasterRequest()->duplicate();
         $fake_request->request->set('ajax_page_state', ['libraries' => implode(',', $cumulative_assets->getAlreadyLoadedLibraries())] + $cumulative_assets->getSettings()['ajaxPageState']);
         $this->requestStack->push($fake_request);
         $event = new FilterResponseEvent($this->httpKernel, $fake_request, HttpKernelInterface::SUB_REQUEST, $html_response);
         $this->eventDispatcher->dispatch(KernelEvents::RESPONSE, $event);
         $html_response = $event->getResponse();
         $this->requestStack->pop();
         // Send this embedded HTML response.
         print $html_response->getContent();
         print $t[1];
         flush();
         // Another placeholder was rendered and sent, track the set of asset
         // libraries sent so far. Any new settings also need to be tracked, so
         // they can be sent in ::sendPreBody().
         // @todo What if drupalSettings already was printed in the HTML <head>? That case is not yet handled. In that case, no-JS BigPipe would cause broken (incomplete) drupalSettings… This would not matter if it were only used if JS is not enabled, but that's not the only use case. However, this
         $final_settings = $html_response->getAttachments()['drupalSettings'];
         $cumulative_assets->setAlreadyLoadedLibraries(explode(',', $final_settings['ajaxPageState']['libraries']));
         $cumulative_assets->setSettings($final_settings);
     }
 }
Example #27
0
 /**
  * @covers ::setData
  * @covers ::set
  * @covers ::initWithData
  */
 public function testSafeStringHandling()
 {
     // Safe strings are cast when using ::set().
     $safe_string = Markup::create('bar');
     $this->config->set('foo', $safe_string);
     $this->assertSame('bar', $this->config->get('foo'));
     $this->config->set('foo', ['bar' => $safe_string]);
     $this->assertSame('bar', $this->config->get('foo.bar'));
     // Safe strings are cast when using ::setData().
     $this->config->setData(['bar' => $safe_string]);
     $this->assertSame('bar', $this->config->get('bar'));
     // Safe strings are not cast when using ::initWithData().
     $this->config->initWithData(['bar' => $safe_string]);
     $this->assertSame($safe_string, $this->config->get('bar'));
 }
Example #28
0
 public function testEntityReferenceTokens()
 {
     $reference = Node::create(['title' => 'Test node to reference', 'type' => 'article', 'test_field' => ['value' => 'foo', 'format' => $this->testFormat->id()]]);
     $reference->save();
     $term_reference_field_value = $this->randomString();
     $term_reference = $this->createTerm($this->vocabulary, ['name' => 'Term to reference', 'term_field' => ['value' => $term_reference_field_value, 'format' => $this->testFormat->id()]]);
     $entity = Node::create(['title' => 'Test entity reference', 'type' => 'article', 'test_reference' => ['target_id' => $reference->id()], 'test_term_reference' => ['target_id' => $term_reference->id()]]);
     $entity->save();
     $this->assertTokens('node', ['node' => $entity], ['test_reference:entity:title' => Markup::create('Test node to reference'), 'test_reference:entity:test_field' => Markup::create('foo'), 'test_term_reference:entity:term_field' => Html::escape($term_reference_field_value), 'test_reference:target_id' => $reference->id(), 'test_term_reference:target_id' => $term_reference->id(), 'test_term_reference:entity:url:path' => '/' . $term_reference->toUrl('canonical')->getInternalPath(), 'test_reference:entity' => $reference->label(), 'test_term_reference:entity' => $term_reference->label()]);
     // Test some non existent tokens.
     $this->assertNoTokens('node', ['node' => $entity], ['test_reference:1:title', 'test_reference:entity:does_not_exist', 'test_reference:does_not:exist', 'test_term_reference:does_not_exist', 'test_term_reference:does:not:exist', 'test_term_reference:does_not_exist:0', 'non_existing_field:entity:title']);
     /** @var \Drupal\token\Token $token_service */
     $token_service = \Drupal::service('token');
     $token_info = $token_service->getTokenInfo('node', 'test_reference');
     $this->assertEquals('Test reference', $token_info['name']);
     $this->assertEquals('Entity reference field.', (string) $token_info['description']);
     $this->assertEquals('token', $token_info['module']);
     $this->assertEquals('node-test_reference', $token_info['type']);
     // Test target_id field property token info.
     $token_info = $token_service->getTokenInfo('node-test_reference', 'target_id');
     $this->assertEquals('Content ID', $token_info['name']);
     $this->assertEquals('token', $token_info['module']);
     $this->assertEquals('token', $token_info['module']);
     // Test entity field property token info.
     $token_info = $token_service->getTokenInfo('node-test_reference', 'entity');
     $this->assertEquals('Content', $token_info['name']);
     $this->assertEquals('The referenced entity', $token_info['description']);
     $this->assertEquals('token', $token_info['module']);
     $this->assertEquals('node', $token_info['type']);
     // Test entity field property token info of the term reference.
     $token_info = $token_service->getTokenInfo('node-test_term_reference', 'entity');
     $this->assertEquals('Taxonomy term', $token_info['name']);
     $this->assertEquals('The referenced entity', $token_info['description']);
     $this->assertEquals('token', $token_info['module']);
     $this->assertEquals('term', $token_info['type']);
 }