Beta phase evaluation
Issue category | Bug: Rewriting Views results is currently broken for multi-valued fields |
---|---|
Issue priority | Major because this is a very commonly used feature. |
Prioritized changes | Improves site builder experience. Potentially improves security as site builders may enable Views PHP/PHP Filter to avoid writing custom field or style plugins. (At least I have! :) |
Disruption | This will affect any module that implements a Views field/style plugin and provides tokens, though the change is minor: replacing [ ] with {{ }} . It will also affect importing existing Views into D8 if the BC layers is removed. |
Problem/Motivation
Views allows its output to be rewritten using [field_name]
style tokens via str_replace()
. These replacements are currently broken due to the value being double-escaped.
Proposed resolution
Convert Views' token replacement code to Twig. This would fix the double-escaped error plus allow site builders much greater flexibility in rewriting Views' output.
For example, a site builder can now use the value of one field to determine the pluralization of another. Previously, this required either a custom field handler or enabling the Views PHP module.
Remaining tasks
User interface changes
The "Replacement patterns" section of the Views UI would need to be updated as well as the description text where Twig replacement patterns can be used.
API changes
Views would internally store field tokens in Twig format, eg: {{ field_name }}
instead of [field_name]
. Modules that implement field or style plugins that use tokens would need to update to the new format.
Original report by @andile2012
Problem/Motivation
In a view, I rewrote the "Tags" field has HTML <h2>[field_tags]</h2>
, but it is just rendering as plain text, not HTML. I also tried to group by this field, and I'm still just getting plain text. I have exported my view and placed it below. Would appreciate any ideas about what to do next.
uuid: d13f4da0-7286-429e-ae54-84dd92eb9688
langcode: en
status: true
dependencies:
entity:
- field.storage.node.body
- field.storage.node.field_tags
module:
- field
- node
id: test_of_grouping
label: 'Test of grouping'
module: views
description: ''
tag: ''
base_table: node
base_field: nid
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
provider: views
display_options:
access:
type: perm
options:
perm: 'access content'
provider: user
dependencies: { }
cache:
type: none
options: { }
provider: views
dependencies: { }
query:
type: views_query
options:
disable_sql_rewrite: false
distinct: false
replica: false
query_comment: false
query_tags: { }
provider: views
dependencies: { }
exposed_form:
type: basic
options:
submit_button: Apply
reset_button: false
reset_button_label: Reset
exposed_sorts_label: 'Sort by'
expose_sort_order: true
sort_asc_label: Asc
sort_desc_label: Desc
provider: views
dependencies: { }
pager:
type: none
options:
items_per_page: null
offset: 0
style:
type: default
options:
grouping:
-
field: field_tags
rendered: true
rendered_strip: false
row_class: ''
default_row_class: true
uses_fields: false
row:
type: fields
options:
default_field_elements: true
inline: { }
separator: ''
hide_empty: false
provider: views
fields:
title:
id: title
table: node_field_data
field: title
provider: node
label: ''
alter:
alter_text: false
make_link: false
absolute: false
trim: false
word_boundary: false
ellipsis: false
strip_tags: false
html: false
hide_empty: false
empty_zero: false
link_to_node: 1
relationship: none
group_type: group
admin_label: ''
dependencies: { }
exclude: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_alter_empty: true
body:
id: body
table: node__body
field: body
relationship: none
group_type: group
admin_label: ''
dependencies:
entity:
- field.storage.node.body
module:
- field
label: ''
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: ''
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: false
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: value
type: text_default
settings: { }
group_column: value
group_columns: { }
group_rows: true
delta_limit: all
delta_offset: '0'
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
plugin_id: field
provider: field
field_tags:
id: field_tags
table: node__field_tags
field: field_tags
relationship: none
group_type: group
admin_label: ''
dependencies:
entity:
- field.storage.node.field_tags
module:
- field
label: ''
exclude: true
alter:
alter_text: true
text: '<h2>[field_tags]</h2>'
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: ''
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: false
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: target_id
type: taxonomy_term_reference_link
settings: { }
group_column: target_id
group_columns: { }
group_rows: true
delta_limit: all
delta_offset: '0'
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
plugin_id: field
provider: field
filters:
status:
value: true
table: node_field_data
field: status
provider: node
id: status
expose:
operator: ''
group: 1
type:
id: type
table: node
field: type
value:
article: article
sorts:
created:
id: created
table: node_field_data
field: created
order: DESC
relationship: none
group_type: group
admin_label: ''
dependencies: { }
exposed: false
expose:
label: ''
granularity: second
title: 'Test of grouping'
header: { }
footer: { }
empty: { }
relationships: { }
arguments: { }
field_langcode: '***LANGUAGE_language_content***'
field_langcode_add_to_query: null
page_1:
display_plugin: page
id: page_1
display_title: Page
position: 1
provider: views
display_options:
field_langcode: '***LANGUAGE_language_content***'
field_langcode_add_to_query: null
path: test-of-grouping
Comment | File | Size | Author |
---|---|---|---|
#39 | 2342287-39_views-rewrite-as-twig.patch | 24.17 KB | mikeker |
#38 | 2342287-36.interdiff.txt | 1.35 KB | mikeker |
#20 | replacement-patterns.png | 12.99 KB | mikeker |
#28 | 2342287-28_23.interdiff.txt | 585 bytes | mikeker |
#28 | 2342287-28_views-rewrite-as-twig.patch | 24.79 KB | mikeker |
Comments
Comment #1
dawehnerSounds like a pretty major bug.
Comment #2
mikeker CreditAttribution: mikeker commentedI'll be working on this at the Amsterdam sprints.
Comment #3
dawehner@mikeker
Let's say at least hi :)
Comment #4
mikeker CreditAttribution: mikeker commentedWith all the changes around
SafeMarkup::set()
, I'd appreciate someone telling me if this is a valid way to fix this...Comment #5
mikeker CreditAttribution: mikeker commentedComment #6
mikeker CreditAttribution: mikeker commentedNote: unit tests are (in my opinion) outside of the scope of this issue because of #2346615: Views doesn't have multi-valued field unit tests.
Comment #7
mikeker CreditAttribution: mikeker commentedJust talked with @dawehner and we agreed that this would be better fixed by rewriting Views' rewrite option to use inline Twig templates instead of a textarea powered by str_replace().
Patch coming soon...
Comment #8
mikeker CreditAttribution: mikeker commentedHere's the quick plan after feedback from @joelpittet:
[field_name]
)str_replace()
the square bracket with double-curly bracketsIdeally any D7 view that currently uses the rewrite option would work without change. But it adds the ability for people to use all the fun features in Twig, along with Twig's autoescaping and safe-by-default approach, to rewrite to their heart's content.
Going forward we could give the option to use a file-based template instead of an inline one.
Comment #9
mikeker CreditAttribution: mikeker commentedAttached patch does what's outlined in #8.
Comment #11
mikeker CreditAttribution: mikeker commentedJust noticed the tag for Amsterdam sprint issues...
Comment #12
mikeker CreditAttribution: mikeker commentedAdded tests so both D7 style (
[field_name]
) and Twig style ({{ field_name }}
) rewrites are covered.Comment #13
Wuk CreditAttribution: Wuk commentedI tried last patch from #12, and it seems that with this patch the issue is fixed and it's working as described in #8.
To test patch I rewrote the "Body" field as
<h2>[body]</h2>
, which without patch resulted as:and rendered as plain text in browser:
With patch result is as expected:
and rendered in browser as expected:
Also tried with twig template syntax
which resulted as expected:
and rendered in browser as expected:
Comment #14
dawehnerI really like the idea, but yeah let's skip the BC layer.
Please let's us not take care about BC at this point.
Comment #15
mikeker CreditAttribution: mikeker commented@Wuk, Thank you for testing the patch and sharing your results.
@dawehner, I agree, backward compatibility makes the code a bit more complicated, but not overly so and I can't think of any way it would impact performance. And it simplifies the upgrade/migrate path. But I'm happy to pull it from the patch if you don't agree it's worth it.
If we remove backward compatibility, we would have to change Views' token system to replace
[token-value]
with{{token_value}}
. Are there any other places in Views that we use token replacement? (I can't think of any off the top of my head...) If there are, perhaps we should convert these to Twig-style rewrites as well? It would be good to get all of them at once!Comment #16
dawehnerMh you are right ... there are though places like in Entity area handlers where you can use the tokens coming from arguments, mhhhhhhh
Comment #17
dawehnerSo let's remove the BC and use different kind of tokens for fields than for the other stuff.
Comment #18
BerdirLooks like a duplicate of #2337747: HTML entities/tags in the item title are double encoded for feeds using fields, has the same fix from what I can see.
Comment #19
dawehnerHuch? This is not the same fix ... given that this issue introduces the suage of twig inside views.
Comment #20
mikeker CreditAttribution: mikeker commentedAdded issue summary.
Comment #21
mikeker CreditAttribution: mikeker commentedComment #22
mikeker CreditAttribution: mikeker commentedAdded Beta Eval to the issue summary.
Comment #23
mikeker CreditAttribution: mikeker commentedAttached patch aims to add Twig to all tokenized fields in Views. Work-in-progress, hoping to get some feedback from the testbot and others.
Comment #25
mikeker CreditAttribution: mikeker commentedMoved the token replacement code into PluginBase and fixed a bunch of tests (updating
[token]
to{{token}}
). Replaced most (all, hopefully) of the hiddenstrtr()
token replacements hidden throughout the code.Comment #26
mikeker CreditAttribution: mikeker commentedHelps to attach the patch...
Comment #28
mikeker CreditAttribution: mikeker commentedFixes the last failing test.
Comment #29
BerdirRe. #18/#19: Sorry yes, the first patch was the same, now we're going for a different solution. Anyway, my conclusion is still correct I think, if we replace that initial fix with the twig replacements, then it might also fix the other issue which needed the same fix?
Comment #30
mikeker CreditAttribution: mikeker commented@Berdir: now that the testbot is green, I'll see if it fixes #2337747: HTML entities/tags in the item title are double encoded for feeds using fields and a few other rewrite issues I saw in the issue queue.
Comment #31
Wuk CreditAttribution: Wuk commentedI did same as in #13 using twig syntax and it works as expected.
Comment #32
mikeker CreditAttribution: mikeker commented@Berdir: No dice... The patch does not fix the issue raised in #2337747: HTML entities/tags in the item title are double encoded for feeds using fields, unfortunately.
Comment #33
BerdirThere is also #2280961: (Views)FieldPluginBase::advancedRender() calls SafeMarkup::set() on a string that it doesn't know to be safe, not sure how this is related :)
Comment #34
dawehnerThat other issue is the general meta issue to get rid of all of them.
This issue fixes a particular subset of existing problems.
Just curious, is there a need to have this method to be public?
Comment #35
mikeker CreditAttribution: mikeker commented@dawehner: Probably not... Protected would be just fine. Will adjust the patch when I get back from holidays.
Comment #36
mikeker CreditAttribution: mikeker commentedChanges public to protected as @dawehner suggestd in #34. Also cleaned up some doc typos.
Comment #38
mikeker CreditAttribution: mikeker commentedHA! I did the interdiff backwards... :)
Use this one.
Comment #39
mikeker CreditAttribution: mikeker commentedsigh... Rerolled against the latest HEAD.
Comment #40
dawehnerAlright, well let's get this in, better sooner than later.
Comment #41
mikeker CreditAttribution: mikeker commentedDoes this need a change record?
Comment #42
mikeker CreditAttribution: mikeker commentedIn case we do, I started a draft change record.
Comment #43
dawehnerI fear :(
Comment #44
mikeker CreditAttribution: mikeker commented@dawehner: details?
Comment #45
dawehnerOH I guess this was a comment conflict :)
Comment #46
alexpottCommitted 552c86c and pushed to 8.0.x. Thanks!
Thanks for adding the beta evaluation to the issue summary.
Comment #48
star-szrJust want to say thanks to everyone who worked on this or helped out in any way, especially @mikeker and @dawehner! This is really exciting, to be able to use Twig filters and other Twig basics from Views UI seems very cool to me.
Comment #49
mikeker CreditAttribution: mikeker commented@Cottser: I appreciate the kind words.
I'm really hoping this allows site builders all sorts of flexibility. The first such idea is showing up in #2055597: Allow multi-value field rendering to pass through Views rewrite only once, which started out as a request to allow different HTML around single values in multi-valued fields. There is a security concern in that issue that I would appreciate feedback on. In short, Xss::filterAdmin encodes '<' and '>' which causes Twig to barf if you have greater/less-than conditionals in your rewrite.
Comment #50
xjmNeed some feedback over in #2396607: Allow Views token matching for validation (and remove dead code):
Twig supports lots and lots of things with the
{{ }}
operators, like:{{ "foo #{1 + 2} baz" }}
However, the code above would silently break that. Should Views only support plain tokens like
{{ foo }}
? Or maybe there is a usecase to support{{ foo.bar }}
or richer expressions like:{{ user.username|e('css') }}
#2396607: Allow Views token matching for validation (and remove dead code) is adding a bit of API for Views token handling, so it would make sense to address this over there.
Comment #51
xjmFiled a minor UI string fix: #2414685: Improve references to Twig tokens in Views UI
Comment #53
Fabianx CreditAttribution: Fabianx as a volunteer and at Tag1 Consulting commentedPost-commit review:
This feels wrong, because while it works for simple cases, twig has many ways to get a variable and this would not allow those.
Proposal:
We know that a token is either %1 or @1, so replace those directly.
Push as context all tokens except those tokens directly.
Nice to have:
Or maybe even make them available as a tokens variable.
So I could write e.g.:
{{ args[1] }} for %1
and
{{ tokens[1] }} for @1 or such?
Overall the straight string replacement while good for keeping BC in a way is a security nightmare.
Because if I do:
%1{%2
e.g. I might suddenly - given enough input possibilities - be able to dynamically change the twig template.
And that is "Twig-Injection-Exploit"?
I know it might not be possible, but if framework manager would allow breaking BC, the best would be to just use:
{{ tokens['%1'] }}
{{ tokens['@1'] }}
instead or find some shorter variable names like:
{{ at[1] }} -- @1
{{ p[1] }} -- %1
===
This also should at least use the new SafeReplace function from the SafeMarkup for #markup patch - once it is available.
I think it would be enough to just have e.g.:
$tokens[$this->options['id']] = Xss::filterAdmin($raw[$id]);
and not deal with either '[]' or '{{' to support my comment above.
Comment #54
Fabianx CreditAttribution: Fabianx as a volunteer and at Tag1 Consulting commentedPer Alex' request posted as follow-up here: #2492839: Views replacement token bc layer allows for Twig template injection via arguments
Comment #55
jonathanshawThis issue is in imminent danger of being rolled-back prior for RC1, because #2492839: Follow-up: Convert Views' %n and !n replacement tokens to Twig syntax has not been addressed (according to #15 in that issue).