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")

Comments

Anonymous’s picture

In queries try using %%:

$title = db_result(db_query('SELECT title FROM {node} WHERE nid = %%d', $nid));
mattconcentric’s picture

I'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.

Ashlar’s picture

Status: Active » Closed (won't fix)

This 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.

codewatson’s picture

Version: 6.x-1.7 » 6.x-1.8
Priority: Major » Normal
Status: Closed (won't fix) » Active

We 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.

Ashlar’s picture

Panels 7.x-3.2 works correctly

codewatson’s picture

Unfortunately 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."

haleagar’s picture

I 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?

codewatson’s picture

Aside 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?

merlinofchaos’s picture

This 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.

codewatson’s picture

I 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?

haleagar’s picture

Oops also my mistake in saying "html entity". Those &amp; 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.

haleagar’s picture

OK 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

if (!is_numeric ( substr($context,0,1) )) {
  $keywords['%' . $keyword] = '';
}

or

if (!ctype_xdigit(substr($context,0,2))) {
  $keywords['%' . $keyword] = '';
}

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'

darvanen’s picture

Version: 6.x-1.8 » 7.x-1.x-dev
Issue summary: View changes
StatusFileSize
new42.84 KB

If 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.

darvanen’s picture

Assigned: Unassigned » darvanen
Status: Active » Needs review
darvanen’s picture

StatusFileSize
new638 bytes

Oops, should have read the patch first.

damienmckenna’s picture

Assigned: darvanen » Unassigned

You should only assign an issue to yourself if you're going to work on it, otherwise please leave it unassigned. Thanks :)

nagy.balint’s picture

This 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.

codewatson’s picture

I 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.

darvanen’s picture

Fair enough @codewatson, I agree. Feel free to provide a patch =D

codewatson’s picture

StatusFileSize
new1.21 KB

Challenge 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

Anonymous’s picture

We've been using the patch in #21 for some time.

Anonymous’s picture

Status: Needs review » Reviewed & tested by the community
nagy.balint’s picture

Any news on this?

mlanth’s picture

Patch #21 works for me!

Had an issue with menu blocks with link title enabled embedded into panels.

rivimey’s picture

#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.

nagy.balint’s picture

This patch was posted a year ago, and is RTBC since 4 months, I dont understand why its not committed yet.

rivimey’s picture

@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 :-)

nagy.balint’s picture

@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.

rivimey’s picture

Issue summary: View changes
rivimey’s picture

rivimey’s picture

damienmckenna’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs tests

Throwing a grand ole "Needs tests" on this because it's something that should be pretty easy to test.

rivimey’s picture

StatusFileSize
new1.45 KB

Adding 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.

rivimey’s picture

Status: Needs work » Needs review
damienmckenna’s picture

StatusFileSize
new2.63 KB

This is the two patches (#21 and #34) in one file, to make it easier for japerry.

damienmckenna’s picture

Status: Needs review » Reviewed & tested by the community
Parent issue: #2785697: Plan for CTools 7.x-1.11 release » #2819121: Plan for CTools 7.x-1.12 release

Lets get it into 1.12.

japerry’s picture

Status: Reviewed & tested by the community » Fixed

Thanks for 36. That passed, and looks good. Committed.

  • japerry committed abb71c0 on 7.x-1.x
    Issue #935168 by Darvanen, codewatson, rivimey, DamienMcKenna: "Use...
sylus’s picture

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.