/**
  * @covers ::preRenderConditionalComments
  * @dataProvider providerPreRenderConditionalComments
  */
 public function testPreRenderConditionalComments($element, $expected, $set_safe = FALSE)
 {
     if ($set_safe) {
         $element['#prefix'] = SafeString::create($element['#prefix']);
         $element['#suffix'] = SafeString::create($element['#suffix']);
     }
     $this->assertEquals($expected, HtmlTag::preRenderConditionalComments($element));
 }
 /**
  * Test that the token replacement in views works correctly.
  */
 public function testViewsTokenReplace()
 {
     $text = '{{ langcode__value }} means {{ langcode }}';
     $tokens = ['{{ langcode }}' => SafeString::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');
 }
Beispiel #3
0
 /**
  * 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']], '#cache_properties' => array_keys(array_filter($expected_results)), 'child1' => ['#markup' => SafeString::create('1')], 'child2' => ['#markup' => SafeString::create('2')], '#custom_property' => SafeMarkup::checkPlain('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");
     }
 }
Beispiel #4
0
 /**
  * Applies a very permissive XSS/HTML filter for admin-only use.
  *
  * Note: This method only filters if $string is not marked safe already. This
  * ensures that HTML intended for display is not filtered.
  *
  * @param string|\Drupal\Core\Render\SafeString $string
  *   A string.
  *
  * @return \Drupal\Core\Render\SafeString
  *   The escaped string wrapped in a SafeString object. If
  *   SafeMarkup::isSafe($string) returns TRUE, it won't be escaped again.
  */
 protected function xssFilterAdminIfUnsafe($string)
 {
     if (!SafeMarkup::isSafe($string)) {
         $string = Xss::filterAdmin($string);
     }
     return SafeString::create($string);
 }
Beispiel #5
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)->will($this->returnValue('/test-route-5'));
     $this->urlGenerator->expects($this->at(1))->method('generateFromRoute')->with('test_route_5', array(), $this->defaultOptions)->will($this->returnValue('/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
     // SafeMarkup::set() call is an intentional unit test for the interaction
     // between SafeMarkup and the LinkGenerator.
     $url = new Url('test_route_5', array());
     $url->setUrlGenerator($this->urlGenerator);
     $result = $this->linkGenerator->generate(SafeString::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);
 }
Beispiel #6
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\Utility\SafeStringInterface|string
  *   The rendered inline template as a SafeString 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 SafeString::create($this->loadTemplate($template_string, NULL)->render($context));
 }
 /**
  * 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*5 = 10 permutations.
  *
  * @todo Cases A5, A6 and A7 are not yet supported by core. So that makes for
  *   only 10 permutations currently, instead of 16. That will be done in
  *   https://www.drupal.org/node/2543334
  *
  * @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\SafeString::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 SafeString::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', 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 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 ten: 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;
 }
 /**
  * {@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 = SafeString::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 SafeStringInterface ? $output : (string) $output;
 }
Beispiel #9
0
 /**
  * Render this field as user-defined altered text.
  */
 protected function renderAltered($alter, $tokens)
 {
     return SafeString::create($this->viewsTokenReplace($alter['text'], $tokens));
 }
 /**
  * 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 SafeString message.
     drupal_set_message(SafeString::create('SafeString with <em>markup!</em>'));
     // Test duplicate SafeString messages.
     drupal_set_message(SafeString::create('SafeString with <em>markup!</em>'));
     // Ensure that multiple SafeString messages work.
     drupal_set_message(SafeString::create('SafeString2 with <em>markup!</em>'));
     // Test mixing of types.
     drupal_set_message(SafeString::create('Non duplicate SafeString / string.'));
     drupal_set_message('Non duplicate SafeString / string.');
     drupal_set_message(SafeString::create('Duplicate SafeString / string.'), 'status', TRUE);
     drupal_set_message('Duplicate SafeString / string.', 'status', TRUE);
     // Test auto-escape of non safe strings.
     drupal_set_message('<em>This<span>markup will be</span> escaped</em>.');
     return [];
 }
 /**
  * Escapes #plain_text or filters #markup as required.
  *
  * Drupal uses Twig's auto-escape feature to improve security. This feature
  * automatically escapes any HTML that is not known to be safe. Due to this
  * the render system needs to ensure that all markup it generates is marked
  * safe so that Twig does not do any additional escaping.
  *
  * By default all #markup is filtered to protect against XSS using the admin
  * tag list. Render arrays can alter the list of tags allowed by the filter
  * using the #allowed_tags property. This value should be an array of tags
  * that Xss::filter() would accept. Render arrays can escape text instead
  * of XSS filtering by setting the #plain_text property instead of #markup. If
  * #plain_text is used #allowed_tags is ignored.
  *
  * @param array $elements
  *   A render array with #markup set.
  *
  * @return \Drupal\Component\Utility\SafeStringInterface|string
  *   The escaped markup wrapped in a SafeString object. If
  *   SafeMarkup::isSafe($elements['#markup']) returns TRUE, it won't be
  *   escaped or filtered again.
  *
  * @see \Drupal\Component\Utility\Html::escape()
  * @see \Drupal\Component\Utility\Xss::filter()
  * @see \Drupal\Component\Utility\Xss::adminFilter()
  */
 protected function ensureMarkupIsSafe(array $elements)
 {
     if (empty($elements['#markup']) && empty($elements['#plain_text'])) {
         return $elements;
     }
     if (!empty($elements['#plain_text'])) {
         $elements['#markup'] = SafeString::create(Html::escape($elements['#plain_text']));
     } elseif (!SafeMarkup::isSafe($elements['#markup'])) {
         // The default behaviour is to XSS filter using the admin tag list.
         $tags = isset($elements['#allowed_tags']) ? $elements['#allowed_tags'] : Xss::getAdminTagList();
         $elements['#markup'] = SafeString::create(Xss::filter($elements['#markup'], $tags));
     }
     return $elements;
 }
Beispiel #12
0
 /**
  * Renders all of the fields for a given style and store them on the object.
  *
  * @param array $result
  *   The result array from $view->result
  */
 protected function renderFields(array $result)
 {
     if (!$this->usesFields()) {
         return;
     }
     if (!isset($this->rendered_fields)) {
         $this->rendered_fields = [];
         $this->view->row_index = 0;
         $field_ids = array_keys($this->view->field);
         // Only tokens relating to field handlers preceding the one we invoke
         // ::getRenderTokens() on are returned, so here we need to pick the last
         // available field handler.
         $render_tokens_field_id = end($field_ids);
         // If all fields have a field::access FALSE there might be no fields, so
         // there is no reason to execute this code.
         if (!empty($field_ids)) {
             $renderer = $this->getRenderer();
             /** @var \Drupal\views\Plugin\views\cache\CachePluginBase $cache_plugin */
             $cache_plugin = $this->view->display_handler->getPlugin('cache');
             /** @var \Drupal\views\ResultRow $row */
             foreach ($result as $index => $row) {
                 $this->view->row_index = $index;
                 // Here we implement render caching for result rows. Since we never
                 // build a render array for single rows, given that style templates
                 // need individual field markup to support proper theming, we build
                 // a raw render array containing all field render arrays and cache it.
                 // This allows us to cache the markup of the various children, that is
                 // individual fields, which is then available for style template
                 // preprocess functions, later in the rendering workflow.
                 // @todo Fetch all the available cached row items in one single cache
                 //   get operation, once https://www.drupal.org/node/2453945 is fixed.
                 $data = ['#pre_render' => [[$this, 'elementPreRenderRow']], '#row' => $row, '#cache' => ['keys' => $cache_plugin->getRowCacheKeys($row), 'tags' => $cache_plugin->getRowCacheTags($row)], '#cache_properties' => $field_ids];
                 $renderer->addCacheableDependency($data, $this->view->storage);
                 $renderer->renderPlain($data);
                 // Extract field output from the render array and post process it.
                 $fields = $this->view->field;
                 $rendered_fields =& $this->rendered_fields[$index];
                 $post_render_tokens = [];
                 foreach ($field_ids as $id) {
                     $rendered_fields[$id] = $data[$id]['#markup'];
                     $tokens = $fields[$id]->postRender($row, $rendered_fields[$id]);
                     if ($tokens) {
                         $post_render_tokens += $tokens;
                     }
                 }
                 // Populate row tokens.
                 $this->rowTokens[$index] = $this->view->field[$render_tokens_field_id]->getRenderTokens([]);
                 // Replace post-render tokens.
                 if ($post_render_tokens) {
                     $placeholders = array_keys($post_render_tokens);
                     $values = array_values($post_render_tokens);
                     foreach ($this->rendered_fields[$index] as &$rendered_field) {
                         // Placeholders and rendered fields have been processed by the
                         // render system and are therefore safe.
                         $rendered_field = SafeString::create(str_replace($placeholders, $values, $rendered_field));
                     }
                 }
             }
         }
         unset($this->view->row_index);
     }
 }
Beispiel #13
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'] = SafeString::create("\n<!--[if {$expression}]>\n" . $prefix);
         $element['#suffix'] = SafeString::create($suffix . "<![endif]-->\n");
     } else {
         // "downlevel-revealed".
         $element['#prefix'] = SafeString::create("\n<!--[if {$expression}]><!-->\n" . $prefix);
         $element['#suffix'] = SafeString::create($suffix . "<!--<![endif]-->\n");
     }
     return $element;
 }
 /**
  * Renders placeholders (#attached['placeholders']).
  *
  * First, the HTML response object is converted to an equivalent render array,
  * with #markup being set to the response's content and #attached being set to
  * the response's attachments. Among these attachments, there may be
  * placeholders that need to be rendered (replaced).
  *
  * Next, RendererInterface::renderRoot() is called, which renders the
  * placeholders into their final markup.
  *
  * The markup that results from RendererInterface::renderRoot() is now the
  * original HTML response's content, but with the placeholders rendered. We
  * overwrite the existing content in the original HTML response object with
  * this markup. The markup that was rendered for the placeholders may also
  * have attachments (e.g. for CSS/JS assets) itself, and cacheability metadata
  * that indicates what that markup depends on. That metadata is also added to
  * the HTML response object.
  *
  * @param \Drupal\Core\Render\HtmlResponse $response
  *   The HTML response whose placeholders are being replaced.
  *
  * @return \Drupal\Core\Render\HtmlResponse
  *   The updated HTML response, with replaced placeholders.
  *
  * @see \Drupal\Core\Render\Renderer::replacePlaceholders()
  * @see \Drupal\Core\Render\Renderer::renderPlaceholder()
  */
 protected function renderPlaceholders(HtmlResponse $response)
 {
     $build = ['#markup' => SafeString::create($response->getContent()), '#attached' => $response->getAttachments()];
     // RendererInterface::renderRoot() renders the $build render array and
     // updates it in place. We don't care about the return value (which is just
     // $build['#markup']), but about the resulting render array.
     // @todo Simplify this when https://www.drupal.org/node/2495001 lands.
     $this->renderer->renderRoot($build);
     // Update the Response object now that the placeholders have been rendered.
     $placeholders_bubbleable_metadata = BubbleableMetadata::createFromRenderArray($build);
     $response->setContent($build['#markup'])->addCacheableDependency($placeholders_bubbleable_metadata)->setAttachments($placeholders_bubbleable_metadata->getAttachments());
     return $response;
 }
Beispiel #15
0
 /**
  * {@inheritdoc}
  */
 public function getCacheableRenderArray(array $elements)
 {
     $data = ['#markup' => $elements['#markup'], '#attached' => $elements['#attached'], '#cache' => ['contexts' => $elements['#cache']['contexts'], 'tags' => $elements['#cache']['tags'], 'max-age' => $elements['#cache']['max-age']]];
     // Preserve cacheable items if specified. If we are preserving any cacheable
     // children of the element, we assume we are only interested in their
     // individual markup and not the parent's one, thus we empty it to minimize
     // the cache entry size.
     if (!empty($elements['#cache_properties']) && is_array($elements['#cache_properties'])) {
         $data['#cache_properties'] = $elements['#cache_properties'];
         // Ensure that any safe strings are a SafeString object.
         foreach (Element::properties(array_flip($elements['#cache_properties'])) as $cache_property) {
             if (isset($elements[$cache_property]) && is_scalar($elements[$cache_property]) && SafeMarkup::isSafe($elements[$cache_property])) {
                 $elements[$cache_property] = SafeString::create($elements[$cache_property]);
             }
         }
         // Extract all the cacheable items from the element using cache
         // properties.
         $cacheable_items = array_intersect_key($elements, array_flip($elements['#cache_properties']));
         $cacheable_children = Element::children($cacheable_items);
         if ($cacheable_children) {
             $data['#markup'] = '';
             // Cache only cacheable children's markup.
             foreach ($cacheable_children as $key) {
                 // We can assume that #markup is safe at this point.
                 $cacheable_items[$key] = ['#markup' => SafeString::create($cacheable_items[$key]['#markup'])];
             }
         }
         $data += $cacheable_items;
     }
     $data['#markup'] = SafeString::create($data['#markup']);
     return $data;
 }