Quantcast
Channel: Issues for Drupal core
Viewing all articles
Browse latest Browse all 292644

Add an easy way to create HTML on the fly without having to create a theme function / template

$
0
0

Problem/Motivation

#1825952: Turn on twig autoescape by default introduced autoescape and with it a mechanism to mark strings as safe. This works great as long as render arrays are used or only t() functions, but it begins to be a problem once user input is involved that needs to be concatenated or modified in some way:

Consider the following code:

<?php
$build
['#markup'] = SafeMarkup::set('<span class="x">'. $my_maybe_secure_value . '</span>');
?>

The problem here is that $my_maybe_secure_value might not be known if its secure at the moment. A way to mitigate that is to use:

<?php
$build
['#markup'] = SafeMarkup::set('<span class="x">'. SafeMarkup::check($my_maybe_secure_value) . '</span>');
?>

but that NEEDS to be done or a XSS will happen. We also discourage direct usage of SafeMarkup::set() as it is marked as internal only function, so we need something better.

This can get complicated very fast:

<?php
          $summary_escaped
= '';
         
$separator = '';
          foreach (
$summary as $summary_item) {
           
$summary_escaped .= $separator . SafeMarkup::escape($summary_item);
           
$separator = '<br />';
          }
         
$field_row['settings_summary'] = array(
           
'#markup'=> SafeMarkup::set('<div class="field-plugin-summary">'. $summary_escaped . '</div>'),
          );
?>

and escaping gets to be something that is difficult, discouraged and an uneasy task.

We could use for this particular example (if SafeMarkup::escape supported arrays - it does not):

<?php
          $summary_escaped
= SafeMarkup::escape($summary);
         
$field_row['settings_summary'] = array(
           
'#markup'=> SafeMarkup::set('<div class="field-plugin-summary">'. implode("<br />", $summary_escaped) . '</div>'),
          );
?>

which is way shorter, but still uses SafeMarkup::set directly, which module authors should never think about.

This would not a problem if the whole section used a template as we then would be getting the autoescape for free and the BIG and BEST solution is to move all those little markup things into a larger encapsulating template.

However we need something in between for until more code can be converted.

Proposed resolution

This issue proposes to introduce a new #type called twig_inline that allows safe creation of markup without calling SafeMarkup::set() directly. This also decreases the chance of single tags getting marked as safe as render arrays are usually just passed up un-rendered to the top page level.
(at least once the SafeMarkup::set() within drupal_render uses the $recursive flag)

Before:

$build['#markup'] = SafeMarkup::set('<span class="x">' . SafeMarkup::escape($possible_unsafe_var) . '</span>');

After:

<?php
$build
['string'] = array(
 
'#type'=> 'inline_template',
 
'#template'=> '<span class="x">{{ var }}</span>',
 
'#context'=> array(
   
'var'=> $possible_unsafe_var,
  ),
);
?>

This also has the advantage that we store less #markup within render arrays, which we also should discourage. While before the render array was only changeable with either re-creating the value or using str_replace, now the template can theoretically still be changed in larger encompassing templates or preprocess functions, which is a slight theming win, too. For forms a simple form_alter() can also be enough.

The more complicated example looks then like:

<?php
          $field_row
['settings_summary'] = array(
           
'#type'=> 'inline_template',
           
'#template'=> '<div class="field-plugin-summary">{{ summary|safe_join("<br />") }}</div>',
           
'#context'=> array('summary'=> $summary),
          );
?>

safe_join is needed here which does automatic escaping before implode - comparable to the SafeMarkup::check() originally done. We had been debating to directly have |join always escape and be a safe filter, but that discussion is still ongoing.

As already discussed in the Motivation section the best would be to move as much of those to larger encompassing structures and templates, but this is probably the best we can do in the short term.

Remaining tasks

* Reviews and discussions.

User interface changes

* None.

API changes

* Addition of #type => 'twig_inline'.

Original report by Fabianx

I wondered if we should allow simple twig inline templates:

Before:

$build['#markup'] = SafeMarkup::set('<span class="x">' . SafeMarkup::escape($possible_unsafe_var) . '</span>');

After:

<?php
$build
['string'] = array(
 
'#type'=> 'inline_template',
 
'#template'=> '<div>{{ var }}</div>',
 
'#context'=> array(
   
'var'=> $possible_unsafe_var,
  ),
);
?>

That gives us autoescape for free and is compiled and cached to disk like normal templates, so pretty performant and not more difficult to use than t().


Viewing all articles
Browse latest Browse all 292644

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>