Drupal 6.19
ctools 6.x-1.7
panels 6.x-3.7
Drupal 7.50
ctools 7.x-1.10
panels 7.x-3.7
I'm not sure if this is related to #531366: Context substitution not working when input format set to php code, but with "Use context keywords" checked on custom content, anything with a % before it will be stripped out if it doesn't match a replacement pattern.
Here are three examples:
Simple text:
There was about 20%-30% difference in price.
What is output:
There was about 20 difference in price.
A link with %20 in it:
<a href="http://www.example.com/here%20is%20a%20pdf.pdf">Click here!</a>
What is output is this:
<a href="http://www.example.com/here.pdf">
php filter and queries (gave me a headache for about 5 hours):
db_query("SELECT * FROM {table} WHERE field = '%s'", "replacement")
What is actually run:
db_query("SELECT * FROM {table} WHERE field = ''", "replacement")
| Comment | File | Size | Author |
|---|---|---|---|
| #36 | ctools-n935168-36.patch | 2.63 KB | damienmckenna |
Comments
Comment #1
Anonymous (not verified) commentedIn queries try using %%:
Comment #2
mattconcentric commentedI've found this with URLs in HTML content that contain '%20' due to linking to files with spaces in the their names in the WYSIWYG editor.
Since I could not turn off keyword substitution, I patched ctools_context_keyword_substitute() in file ctools/includes/context.inc to ignore any keywords that started with '20', which preserves my '%20's.
It's not perfect, but I can't see many cases of genuine keywords starting with numbers, so it should be safe enough in my case.
Comment #3
Ashlar commentedThis bug report has not been active for over six months. In an effort to clean-up the issue queue this item has been closed. If your modules are current and the report is still relevant please feel free to change the Status back to active.
Comment #4
codewatson commentedWe got in the habit of unchecking that box because of it, but i just checked and can confirm that this is still the case in the 6.x-1.8 version.
Ideally if there is no keyword that can be matched, it shouldn't just strip out the % and what's after it, it should just leave it be.
Comment #5
Ashlar commentedPanels 7.x-3.2 works correctly
Comment #6
codewatson commentedUnfortunately not, i did a fresh install of drupal 7/panels 3.2/ctools 1.0 and put this example in a custom content pane: "There was about 20%-30% difference in price." and again this is what was output: "There was about 20 difference in price."
Comment #7
haleagar commentedI ran into this on a site update, so in ctools "6.x-1.2" this was not a problem but in ctools "6.x-1.8" it is.
I looked at the function ctools_context_keyword_substitute() identified by matthine
it looks like previously if there was no matching context there was no replacement added to the $keywords array.
but now there is a replacement of empty string added.
On can simply choose not to replace the string at all if there is no value to replace it with instead of replacing it with and empty string. It seems like a valid choice either way, but with significantly different results.
To do this simply comment out the line $keywords['%' . $keyword] = ''; in ctools_context_keyword_substitute()
Or alternately but with much more overhead, check to see if the '%' . $keyword contains valid html entity and if so do not replace it.
Thoughts? Is there a compelling reason that you should replace the supposed keyword with and empty string?
Comment #8
codewatson commentedAside from perhaps protecting the name of a keyword, I really don't see why you would want to return an empty string if a keyword is not matched?
Comment #9
merlinofchaos commentedThis is primarily useful in things like titles and such when you have potentially empty keywords due to optional contexts. It is actually the intended behavior.
So it might be best if we check to see if it is, in fact, an html entity before deciding that we can't replace it. A simple check for a ';' as the last character of the keyword should suffice for that purpose.
Comment #10
codewatson commentedI see, but I think the main issue is not that a keyword is empty, it is that the keyword doesn't exist in the first place? As you said, if the context exists but is empty that is one thing, but shouldn't that be handled differently from a context that doesn't exist at all?
I guess the way i would expect keywords to function is similar to the Token Filter module, in that if a token exists but is empty, then yes, an empty string is returned, but if the token itself doesn't exist, then it leaves the original input alone?
Comment #11
haleagar commentedOops also my mistake in saying "html entity". Those
&type values are not a problem, it's url encoded hex codes, %20 %2f %ca etc..Due to the fact that one might have a keyword like %catalog I have not thought of a shortcut for identifying this case short of urldecode()
We can't assume that the whole found keyword is just one hex code since the text is likely to be something like this.
href="my%20file%20name.pdf"so the keyword match found will be "%20file%20name"
On could make this check
if (urldecode ( '%' . $keyword ) == '%' . $keyword)but I don't know how expensive and extra urldecode is here, but that would solve the issue for my problem cases with a less drastic change in behavior.
Comment #12
haleagar commentedOK so still and issue, I'm patching after an update.
But I thought of a couple alternate checks that I believe may be lighter weight.
Still not perfect but maybe enough to trigger someone else to think of a safe logical check?
I am of the opinion that it is a problem that I could not use context keywords in a text field that also includes a URL encoded string. Most end users when they encounter this are baffled.
in place of
$keywords['%' . $keyword] = '';check
or
The problem is the first above still fails on 20%-30% since - is not a number or hex.
The problem with the second is that some context like $data would match hex 'da'
Comment #13
darvanenIf all you want to do is ignore potential keywords that start with numbers and/or hyphens, you can just change the regex that searches for keywords from this:
if (preg_match_all('/%(%|[a-zA-Z0-9_-]+(?:\:[a-zA-Z0-9_-]+)*)/us', $string, $matches)) {to this:
if (preg_match_all('/%(%|[a-zA-Z_]+(?:\:[a-zA-Z0-9_-]+)*)/us', $string, $matches)) {Works for me. I'm not aware of any situations in which actual keywords start with numbers or hyphens so this should do the trick.
Comment #14
darvanenComment #15
darvanenOops, should have read the patch first.
Comment #16
damienmckennaYou should only assign an issue to yourself if you're going to work on it, otherwise please leave it unassigned. Thanks :)
Comment #18
nagy.balint commentedThis patch (#15) solves my problem, as we had the same issue that we had url encoded characters in a markup attribute, and that was stripped by this function without the patch.
Comment #19
codewatson commentedI guess I still think the better behavior would be to return an empty string if a context exists but is empty, and do no replacement if a context does not exist, instead of trying to mess with a regex for various use cases.
Ex:
%existing_context (value = "ABCD") - replaced with "ABCD"
%existing_context (value = empty/null) - replaced with ""
%no_context (does not exist) - not replaced, ie returns "%no_context"
A side benefit of this is if you misstype a context, it will be less confusing to troubleshoot when the context itself is left in the text instead of an empty string and trying to figure out why nothing appears.
Comment #20
darvanenFair enough @codewatson, I agree. Feel free to provide a patch =D
Comment #21
codewatson commentedChallenge accepted!
Take a look, I haven't tested it too much, but seems to behave like I expect it to:
%ContextExists (value = "ABCD") - replaced with "ABCD"
%ContextExists (value = empty/null) - replaced with ""
%NoContext (does not exist) - not replaced, i.e. returns "%NoContext"
So far all the cases mentioned in this thread seem to work, here's what I've run to test it:
Exists (%%user:uid) | 1
Empty (%%token:site:slogan) |
Doesn't (%%foo:bar) | %foo:bar
Percent only (%%) | %
http://www.example.com/here%20is%20a%20pdf.pdf | http://www.example.com/here%20is%20a%20pdf.pdf
"SELECT * FROM {table} WHERE field = '%s'", "replacement" | "SELECT * FROM {table} WHERE field = '%s'", "replacement"
There was about 20%-30% difference in price. | There was about 20%-30% difference in price.
href="my%20file%20name.pdf" | href="my%20file%20name.pdf"
20% | 20%
%20 | %20
20%20 | 20%20
%%user:created | Wed, 05/27/2015 - 12:34
%%user:edit-url | http://salsaapi.codewatson.com/user/1/edit
%%user:mail | webmaster@codewatson.com
%%user:last-login | Wed, 05/27/2015 - 12:35
%%user:name | admin
%%user:url | http://salsaapi.codewatson.com/user/1
%%user:uid | 1
%%token:current-user:created | Wed, 05/27/2015 - 12:34
%%token:current-user:edit-url | http://salsaapi.codewatson.com/user/1/edit
%%token:current-user:mail | webmaster@codewatson.com
%%token:current-user:last-login | Wed, 05/27/2015 - 12:35
%%token:current-user:name | admin
%%token:current-user:url | http://salsaapi.codewatson.com/user/1
%%token:current-user:uid | 1
%%token:date:custom |
%%token:date:long | Monday, June 8, 2015 - 11:38
Comment #22
Anonymous (not verified) commentedWe've been using the patch in #21 for some time.
Comment #23
Anonymous (not verified) commentedComment #24
nagy.balint commentedAny news on this?
Comment #25
mlanth commentedPatch #21 works for me!
Had an issue with menu blocks with link title enabled embedded into panels.
Comment #26
rivimey#Patch in #21 applies cleanly to current 7.x head.
code sniffer comes up with a couple of issues:
For:
if (isset($context_keywords[$keyword])) {
it suggests using array_key_exists(), and for:
$keywords['%' . $keyword] = ctools_context_convert_context(...
it notes that the function can return void (early on in the function is a "return;').
Not included in the changed lines, but in the same block, it notes that for:
if (preg_match_all('/%(%|[a-zA-Z0-9_-]+(?:\:[a-zA-Z0-9_-]+)*)/us', $string, $matches)) {
the 's' option on the regex is ambiguous, because there is no '.' in the pattern itself.
Overall, I don't think any of those issues are significant enough to defer applying the patch.
Comment #27
nagy.balint commentedThis patch was posted a year ago, and is RTBC since 4 months, I dont understand why its not committed yet.
Comment #28
rivimey@nagy.balint, it wasn't committed because nobody volunteered to get it committed. Offering your own time to help (what I am doing right now) is a good way to get things moving :-)
Comment #29
nagy.balint commented@rivimey, Well if an issue is rtbc then it should either be committed or it should be set back to needs work with a reason. But if there is no maintainer to look at rtbc issues then it does not matter what i do. It will never be committed.
Comment #30
rivimeyComment #31
rivimeyComment #32
rivimeyComment #33
damienmckennaThrowing a grand ole "Needs tests" on this because it's something that should be pretty easy to test.
Comment #34
rivimeyAdding the tests Damien requested :-)
THIS PATCH ONLY ADDS TESTS: YOU STILL NEED #21 to fix the issue.
This is done so it's easy to do before/after patch testing.
Tested using
php ../../../../scripts/run-tests.sh --verbose --class CtoolsContextKeywordsSubstitutionTestCase---- CtoolsContextKeywordsSubstitutionTestCase ----
Status Group Filename Line Function
--------------------------------------------------------------------------------
Pass Other context.test 13 CtoolsContextKeywordsSubstitutionTe
Enabled modules: ctools
Pass Other context.test 82 CtoolsContextKeywordsSubstitutionTe
Multi-level token has been replaced. Colon left untouched.
Pass Other context.test 82 CtoolsContextKeywordsSubstitutionTe
Keyword and converter have been replaced.
Pass Other context.test 82 CtoolsContextKeywordsSubstitutionTe
Keyword after escaped percent sign left untouched.
Pass Other context.test 82 CtoolsContextKeywordsSubstitutionTe
Multiple substitutions have been replaced.
Pass Other context.test 82 CtoolsContextKeywordsSubstitutionTe
Colon after keyword and converter left untouched.
Pass Other context.test 82 CtoolsContextKeywordsSubstitutionTe
Escaped percent sign after keyword and converter left untouched.
Pass Other context.test 82 CtoolsContextKeywordsSubstitutionTe
Keyword after escaped and unescaped percent sign has been replaced.
Pass Other context.test 82 CtoolsContextKeywordsSubstitutionTe
Non-existant context ignored.
Pass Other context.test 82 CtoolsContextKeywordsSubstitutionTe
Non-keyword percent sign left untouched.
Pass Other context.test 82 CtoolsContextKeywordsSubstitutionTe
HTTP URL escape left untouched.
Pass Other context.test 82 CtoolsContextKeywordsSubstitutionTe
HTTP URL escape 2 left untouched.
Pass Other context.test 82 CtoolsContextKeywordsSubstitutionTe
Non-keyword percent sign left untouched.
Pass Other context.test 82 CtoolsContextKeywordsSubstitutionTe
Non-keyword percent sign left untouched.
Comment #35
rivimeyComment #36
damienmckennaThis is the two patches (#21 and #34) in one file, to make it easier for japerry.
Comment #37
damienmckennaLets get it into 1.12.
Comment #38
japerryThanks for 36. That passed, and looks good. Committed.
Comment #40
sylus commentedThis commit caused a regression with panels titles.
New issue filed here:
https://www.drupal.org/node/2830559
#2830559: Regression: CTools Page Title %title in text (Use context keywords" strips out anything after a %)