Spin-off from #2341357: Views entity area config is not deployable and missing dependencies
Problem/Motivation
Views supports two types of tokens, argument placeholders that correspond to View arguments (e.g. %1
or !3
), and global tokens. The use of these tokens affects what is valid input for the View, however, Views sometimes checks for only the opening character of the token, and other times does not validate for token input at all. See #2392823: [meta] Much Views UI input is not validated.
Additionally, the existing method StylePluginBase::usesTokens()
is never used or tested in D8 HEAD.
Beta phase evaluation
Issue category | Task because it does not fix any bug by itself. |
---|---|
Issue priority | Normal because it is a simple API addition to make Views input validation simpler, but the related issues are not blocked by this addition. |
Prioritized changes | The main goal of this issue is reducing fragility by providing a single API for matching tokens in Views and removing related unused, untested code. Also, the patch simplifies a critical and a major. Therefore, this could be considered a prioritized change during the beta. |
Disruption | Not disruptive for core or contributed modules as it is only an API addition, and only requires limited changes within core for consistency. |
Proposed resolution
Add API methods for matching tokens in Views.
Remaining tasks
- There are still several
strpos($path, '%')
(without !) about in path-checking code.
Task | Novice task? | Contributor instructions | Complete? |
---|---|---|---|
Add automated tests | Instructions | ||
Review patch to ensure that it fixes the issue, stays within scope, is properly documented, and follows coding standards | Instructions |
User interface changes
None.
API changes
- New methods on
PluginBase
. DisplayPluginBase::getArgumentsToken()
renamed toDisplayPluginBase::getArgumentToken(
)
Comment | File | Size | Author |
---|---|---|---|
#91 | 2396607-91-88reroll.interdiff.txt | 607 bytes | mikeker |
#91 | 2396607-91-vdc-token-cleanup.patch | 45.94 KB | mikeker |
#88 | 2396607-88-83.interdiff.txt | 6.28 KB | mikeker |
#88 | 2396607-88-vdc-token-cleanup.patch | 44.7 KB | mikeker |
#83 | 2396607-83-78.interdiff.txt | 4.08 KB | mikeker |
Comments
Comment #1
xjmComment #2
xjmNote that this change is in scope because it ensures that the style plugin is initialized when this is used.
These optimizations are added based on the removed method, but maybe they're not needed.
Comment #3
xjmNW for unit tests which I'm trying to add next. :)
Comment #4
dawehner<3
Are you sure we really need this wrapper methods here? I also still don't see reason why they should be public?
Can you explain why we have to check for global tokens, even these tokens here don't support global tokens at all? Note: At least at the moment we just support row level and arguments tokens.
Comment #5
xjmWith the wrapper methods I was just following the pattern of... some other method on the class. I also debated whether they were actually necessary. Probably simpler to just take them out if you don't see a reason.
Hm? The code in head supports global tokens, or at least it checks for
[
, so I just converted it to the new methods. Also that hunk is inStylePluginBase
. Maybe I'm using the words "global token" incorrectly and the method name is wrong? I was just using it to refer to the utilityToken
class tokens.Comment #6
xjmComment #7
dawehnerSo yeah we use the same language here.
Let's have a look at
StylePluginBase::tokenizeValue()
:"!" and "%" are used for
$this->view->build_info['substitutions']
, seeViewExecutable::_buildArguments
."[" points to row level tokens aka. field tokens, so for example things like "[title]". This is coming from
What we both refer as "global tokens" are just available for area plugins, meh, its kind of a mess.
Comment #8
xjmOh ah, I completely forgot the
[my_other_field_value]
tokens. Is that "row-level" tokens? Edit: Yes, per above. Maybe that is a third method or?Comment #9
xjmI guess we should add
hasRowToken()
and maybe drophasGlobalToken()
. So inFieldPluginBase
:Are there any hard limitations on what the field handler identifiers there (
$field
) can be? Alphanumeric & underscore (or is that just convention)? Does the plugin system limit that (or does Views)?Edit: i.e., is the regex something less than
/\[.*\]/
.Comment #10
dawehnerWell, ideally we still use that maybe to validate the tokens of the global ones for the area?
This is a good question! By default views uses the key in hook_views_data. In case an item already exists
ViewExecutable::generateHandlerId
is used,so its pretty much
[a-z_0-9]+
I would say?Comment #11
xjmMmmm also:
(Looking particularly at
$tokens['%' . $token_string] = strip_tags(String::decodeEntities($val));
.)Comment #12
xjmDoes this actually limit it to
[a-z_0-9]+
? For automatic relationships this would I guess depend on the DB driver's limitations. http://dev.mysql.com/doc/refman/5.0/en/identifiers.html is a lot more permissive than one would expect. And for exposing relationships, it almost looks like there's nothing to prevent you from giving it any crazy string name...This is turning into one of those questions I wish I hadn't thought to ask. :P
Comment #13
dawehnerUrgs you are right, it could be quite a lot. Let's just go with your previous regex, ... its kinda good enough :)
This is the code calling this ... so we also have all query attributes available, ... its great to learn something new every day.
On top of that, we also have the self tokens. What are self tokens, ... those are like row level tokens, but for the rendering
within a single field. For fieldapi fields it used to be the raw data and the actual rendered data, for example. This tokens could be just
used inside the field itself.
Comment #15
xjmThe syntax error is pre-5.5 only; took me a bit of head-scratching to remember that
empty()
was a bit precious.Good thing we have 5.4 bots, I guess.
FWIW the Token API only cares that we have some non-space-containing key followed by a colon followed by anything between square brackets, so apparently a permissive regex isn't too crazy:
(That however will match even
[:]
which looks like a bug. Filed #2396793: Token API will match tokens with empty types or tokens, e.g. [:invalid].)Comment #18
xjmLet's try that again. (TIL that running all phpunit tests doesn't hit syntax errors in TokenizeAreaPluginBase.)
Comment #19
xjmComment #20
xjmHey look, when we write unit tests, we find bugs in our code.
Comment #21
xjmNW for the rest of the tests.
Comment #22
xjmNow with the rest of the tests.
Comment #24
dawehnerNote: the Serializer style also overrides the constructor
<3
Note: ideally a unit test would mock the token class, so this test is independent of the implementation of the other one. it would also mean less mocking crazyness here
c&p? Its not used at all
Comment #25
dawehnerI guess my druplicon message would be too short, here is an actual example:
Comment #26
xjmUpdating for #2342287: Allow Twig in Views token replacement.
Comment #27
xjmSo reading the Twig docs, all sorts of craziness is possible with the
{{ }}
operators, e.g.:{{ "foo #{1 + 2} baz" }}
What do we actually want to support in Views? Should we restrict it to only
{{ token_name }}
or pass through any old code to Twig?I'll ask some of the Twig folks to give feedback
Comment #28
dawehnerAs said on IRC I think the code we have is fine.
So this is about converting available tokens, to be passed along to twig.
In
FieldPluginBase::getRenderTokens()
we generate the available tokens. For UI purposes we show tokens with "{{ }}" around them,also see
Views simply provides this limited amount of tokens, so it never happens that you end up with
{{ "foo #{1 + 2} baz" }}
in that piece of code.
Afaik though if also
foo
andbar
are available the inline_template should be able to use it, shouldn't it?Comment #29
xjmSo @dawehner, @Cottser, and I discussed this earlier. Any old Twig syntax you enter in a Views field is evaluated, e.g.:
will render as:
And if you enter invalid Twig syntax, the View will throw an exception, e.g.
Cottser tested and one thing you can't do is nest double curlies, i.e.
{{ foo {{ invalid }} bar }}
So for this issue, I'll match anything-not-double-curlies in the regex, but we probably want to figure out whether we want to support any and all Twig code, or want to restrict it to simple tokens. One possibility Cottser suggested:
Comment #30
star-szrThanks for writing that up @xjm! I just want to make sure it's clear that the double/nested curlies is invalid Twig in the first place so it's not a limitation imposed by the current implementation :)
Comment #31
star-szrAlso we could potentially evaluate whether the input is valid Twig before saving (as part of validation):
(via https://groups.google.com/forum/#!topic/twig-users/idoJ0tJSZKY).
The sandbox mode might be interesting to consider with inline templates in general to limit the scope of functionality: http://twig.sensiolabs.org/doc/api.html#sandbox-extension
Comment #32
xjmHere's a patch for #29. Edit: Forgot that I was going to move these methods to PluginBase so we can use them in
PluginBase::viewsTokenReplace()
. Doing that now.I think #31 might make sense as part of #2392823: [meta] Much Views UI input is not validated or whatever followup we make to sort what Twig should do. Thanks @Cottser.
Comment #33
xjmOkay, so upon further inspection researching #2342287: Allow Twig in Views token replacement, all of our token handling logic is even more all over the place than I thought it was before!
DisplayPluginBase::getArgumentsTokens()
is not quite English. Renamed togetArgumentTokens()
.FieldPluginBase::getRenderTokens()
needlessly duplicated logic inDisplayPluginBase::getArgument[s]Tokens()
. Fixed to just call that method, since it already needed to use the display handler anyway.etArgToken()
andgetTwigToken()
to match those existing method names onDisplayPluginBase
andFieldPluginBase
.PluginBase,
so that they can be used inPluginBase::viewsTokenReplace()
.StylePluginBase::tokenizeValue()
now seems slightly redundant, misnamed, and out of place, but I haven't addressed that yet.PluginBase
too. I've left those alone for now as they are using calls to\Drupal::token()
and that should be injected those places too. Constructor injection would seem kinda noisy/disruptive; I wonder if it could use setter injection or something?Comment #34
xjmComment #35
xjmComment #36
dawehnerYou know, things are a step by step process.
Mh? So we want to express that its getting all tokens from all arguments :) Btw. I also trust earls english.
Setter injection on the plugin manager level could work, but yeah you still want to make it optional.
Seriously, given how much methods we have, I think we should extract them and don't rely on inheritance. We could introduce a new object, maybe make it available on the view executable and use it. I hate to place more and more and more into the base class.
Comment #37
xjmAgreed, that makes a lot of sense I think and will clean up the tangle. So... any suggestion on what said object is called and where it lives? ViewsTokenSomething?
Comment #38
mikeker CreditAttribution: mikeker commentedIn answer to your earlier question , I think the tokens that Views supplies should be simple tokens -- strings and arrays -- that can be used in the replacement text in any way the site builder wants. I think I'm saying the same thing that @dawehner said in #28.
One issue, though, is that Views' rewrite text is passed through
Xss::filterAdmin()
which encodes '<' and '>', otherwise valid in in Twig blocks. See #2055597: Allow multi-value field rendering to pass through Views rewrite only once, specifically comment #11.(TL/DR: While not (yet) currently available, multi-valued Views tokens would be hugely useful to site builders. But
filterAdmin()
encodes the '>' in{% if field_tags_multiple|length > 1 %}
which raise a compile error in Twig and would cripple their usefulness.)I'm pretty sure that's out of scope for this issue, but I thought I'd bring it up in case I'm wrong. :)
Comment #39
xjmThanks @mikeker!
Here's a start at moving the token functionality into its own object with some more central documentation. I added some @todo on the class for the numerous other methods in various Views plugins that could do with some refactoring as well. No interdiff because it's not helpful with all the moved code. (Need to add back the unit test coverage in a unit test for the new class still.) Few questions:
%1
vs.!1
for argument tokens. I believe @dawehner explained that the!
tokens remove markup for places that can't have markup, like a path, but still a bit confused. InString::format()
etc.,!
denotes the raw value and%
denotes the sanitized value -- but I think Views is doing the reverse, stripping markup from the!
version:Also, this is what it says in the UI:
The difference between "title" and "input" is not clear to me.
Part of what I'd like to do is add methods specific to
!
and%
and trying to figure out what those methods should be called.FieldPluginBase::getTokenValuesRecursive()
. The documentation for what it does is very clear but I don't entirely understand why we do it. :) It appears to be a feature of argument matching that I never knew about maybe? Also it's specific to argument tokens, but yetDisplayPluginBase::getArgumentTokens()
doesn't use it.TokenizeAreaPluginBase::tokenForm()
did not get updated for #2342287: Allow Twig in Views token replacement so we have a regression there I think.Comment #41
mikeker CreditAttribution: mikeker commented#4 leads us into the issue I didn't want to raise in #2342287: Allow Twig in Views token replacement, but since you brought it up... :)
The header/footer (and perhaps other) areas include Drupal tokens (aka: core tokens, such as site-name), while field rewrites only include row-based tokens (aka Views' tokens). Which leads me to my secret mission: provide Twig tokens and Twig replacements for all token fields in core.
Comment #42
mikeker CreditAttribution: mikeker commentedFixes typo in #39.
Comment #45
xjmI really seem incapable of rolling a patch this morning.
Comment #47
xjmWith #42...
Comment #48
xjmComment #50
mikeker CreditAttribution: mikeker commentedI assume the @todo suggestion is a concern about zapping valid curly brackets that are not token delimiters? Perhaps a more surgical option than str_replace would be
$token = trim($token, "{} \t\n\r\0\x0B");
(" \t\n\r\0\x0B" is the default value for the second param.)
My only objection to using
preg_replace
is performance, which is a minor objection at best.Comment #51
xjm@alexpott says this voodoo trait is necessary to make D8 work.
Comment #52
xjmComment #53
xjmSince a given view typically will not have more than 5-20 field tokens, I think it's probably not needed to micro-optimize for that. Clever solution though! In either case I'm probably going to abstract it into its own method so we can change it as needed.
Comment #55
mikeker CreditAttribution: mikeker commentedTrue, but this is run on each field that's rewriten in each row displayed in a view. That could start to add up, though I have no numbers to back that up. Regardless,
is a good idea and let's us fiddle with it later.
Comment #56
mikeker CreditAttribution: mikeker commentedI was trying to look into the testbot errors and ran into something strange...
getTokenParser()
was returning NULL. SomehowViewExecutable
was being instantiated, but__construct()
wasn't being called? SinceViewExecutable
isn't extended, I have no idea how that can happen...This only happens in the ViewsUI preview, not when rendering a the view for real.
Comment #57
mikeker CreditAttribution: mikeker commentedThis will probably help with a lot of the testbot failures... :)
Comment #59
mikeker CreditAttribution: mikeker commentedThis should fix one more test...
I think the remaining test failures are actually related to #56. There is some way to instantiate a
ViewExecutable
without passing it aToken
.Comment #61
mikeker CreditAttribution: mikeker commented#59 no longer applies....
Comment #62
AjitSJust rerolling for now.
Comment #64
AjitSImplemented the missing function from the implemented interface.
Comment #65
AjitSAnd renamed
getArgumentsTokens()
togetArgumentTokens()
as mentioned in the issue summary.Comment #68
AjitSThe function was already present. Just needed the rename to be done. Which is why the PHP error. Retrying.
Comment #70
mikeker CreditAttribution: mikeker commentedWorking my way through testbot failures. This cleans up some namespacing issues...
Comment #71
mikeker CreditAttribution: mikeker commentedComment #73
mikeker CreditAttribution: mikeker commentedOK, #70 seems to get us back to where we were in #59...
But the remaining failures are weird. When instantiated through the
ViewsUI
class, theViewsExecutable
class doesn't have aTokenParser
associated with it. To repro the error: create a new view with page display and all the default settings, then click the Preview button and look at the resulting AJAX.Comment #74
mikeker CreditAttribution: mikeker commentedWhat takes me hours (and gets me nowhere) takes @dawehner mere seconds in IRC...
Let's see what the testbot thinks about this one.
Comment #78
mikeker CreditAttribution: mikeker commentedYuck. The 70k patch in #2409209: Replace all _url() calls beside the one in _l() and the 38k patch in this issue both added a fourth param to
ViewExecute::__construct()
. So merging one with the other resulted in a silent fail -- at least until the test were run.<rant>This is why I really hate our anachronistic, patch-based system. If there was a history to either of these changes, it would be easy to narrow down the change to a given commit (assuming the devs involved were good about committing early and often). Instead we're left with resolving the difference between two large patches.</rant> ... Sorry. Back to our topic at hand.
I think I've gotten everything straightened out. Let's see what testbot thinks.
Comment #79
mikeker CreditAttribution: mikeker commentedNot sure what happened to the interdiff...
Comment #81
mikeker CreditAttribution: mikeker commentedAck, missed the ViewExecutableFactory class...
Comment #83
mikeker CreditAttribution: mikeker commentedHere's the rest of the ViewExecutableFactory that I managed to miss on my previous attempt. Interdiff against #78 since #81 was only half the fix...
This should bring us down to just a couple of test failures. Sorry about the noise -- I'm going to bed soon. Promise.
Comment #85
star-szrI turned my comment in #31 into a sub-issue of the meta: #2457257: Use Twig parser to detect errors in Twig code entered in Views UI
I'm guessing @xjm doesn't want/needs this assigned any longer, so unassigning.
Comment #88
mikeker CreditAttribution: mikeker commentedAdds tests for the new TokenParser class: unit tests for
hasTokens()
and functional test forviewsTokenReplace()
. Haven't cleaned up any of the existing test failures so I don't expect this patch to pass but running against the testbot to ensure the newly added tests don't fail either.Comment #89
mikeker CreditAttribution: mikeker commented@Cottser: Thanks for adding the followup for #31 -- otherwise that would've gotten lost in all the refactoring!
Comment #91
mikeker CreditAttribution: mikeker commentedReroll of #88. Plus it adds the token parser back to the
ViewExecutable
object when unserializing. That should fix the BulkFormTest that was failing and maybe the remaining ones.Let's see what the testbot says...
Comment #96
jonathanshawRefactoring here should probably also consider the implications of #2665766: {% %} twig syntax is only parsed in text area handler when a {{ }} token is present, and that twig can be used more than token replacement.