We use cookies to improve your experience. No personal information is gathered and we don't serve ads. Cookies Policy.

ExpressionEngine Logo ExpressionEngine
Features Pricing Support Find A Developer
Partners Upgrades
Blog Add-Ons Learn
Docs Forums University
Log In or Sign Up
Log In Sign Up
ExpressionEngine Logo
Features Pro new Support Find A Developer
Partners Upgrades
Blog Add-Ons Learn
Docs Forums University Blog
  • Home
  • Forums

Conditional conundrums

Developer Preview

Low's avatar
Low
407 posts
11 years ago
Low's avatar Low

Interesting developments with 2.9! However, the new conditional parsing does break a little something I was using quite a bit, specifically with my add-on Low Search. Take this bit of template, for example:

{exp:low_search:form query="{segment_2}"}
 ...
 <select name="category[]" multiple>
  {exp:channel:categories style="linear"}
   <option value="{category_id}"{if category_id IN ({low_search_category})} selected{/if}>
    {category_name}
   </option>
  {/exp:channel:categories}
 </select>
 ...
{/exp:low_search:form}

The main issue here is the special IN conditional. This is a way of working around EE’s parse order, specifically the way it handles nested tags. Here the low_search:form tag is parsed first, then the channel:categories tag is parsed. The {low_search_category} variable contains a pipe-separated list of category IDs, and is set by the outer tag.

For example, say {low_search_category} equates to 1|2|3. Then the conditional will look like this when the outer tag parses its variables:

{if category_id IN (1|2|3)} ... {/if}

Low Search then rewrites that conditional to:

{if category_id == 1 OR category_id == 2 OR category_id == 3} ... {/if}

…making it a valid (advanced) conditional, which got parsed after tags were processed.

This allowed me to have conditionals for multi-valued variables that were known to an outer tag, but needed to be passed on to an inner tag, and worked a treat. I could even write a NOT IN conditional and rewrite that to {if var != 1 AND var != 2 …}.

Needless to say, the new conditional parser breaks this. I’m hoping you will help me making this work in 2.9 in some way or another.

       
Sobral's avatar
Sobral
87 posts
11 years ago
Sobral's avatar Sobral

Am I wrong to think that the “easiest” solution is to add the IN operator to the core?

       
Pascal Kriete's avatar
Pascal Kriete
2,589 posts
11 years ago
Pascal Kriete's avatar Pascal Kriete

Oh my.

Low: If we came up with a solution that required you to replace the IN, is that even an option?

Robson: Have to be careful what path that puts us on; not everyone writing templates is a programmer. Some struggle with what most programmers would consider relatively simple conditionals.

       
Low's avatar
Low
407 posts
11 years ago
Low's avatar Low

Robson: Not quite. All the other operators are standard PHP operators or math equations. In PHP this is doesn’t work:

if ('some-string' IN ('foo', 'bar', 'lorem', 'ipsum'))

Basically, the IN conditional mimics MySQL’s “WHERE foo IN (1,2,3)” syntax or is similar to PHP’s “if (in_array($foo, [1, 2, 3]))”. IN inherently is not a valid operator in PHP, so some rewriting must be done.

       
Low's avatar
Low
407 posts
11 years ago
Low's avatar Low

Pascal: I guess, yes. The IN syntax is just something I came up with. The underlying problem is using conditionals with nested tags, as shown in the examples above. I’m OK with working with you to solve that issue, which I think will benefit all add-on devs.

       
Low's avatar
Low
407 posts
11 years ago
Low's avatar Low

Of course, I’d appreciate if the IN conditionals could stay intact. I know that Mark Croxton also uses this syntax. FWIW, this is the code I use to rewrite the conditionals:

function low_prep_in_conditionals($tagdata = '')
{
 if (preg_match_all('#'.LD.'if (([\w\-_]+)|((\'|")(.+)\\4)) (NOT)?\s?IN \((.*?)\)'.RD.'#', $tagdata, $matches))
 {
  foreach ($matches[0] AS $key => $match)
  {
   $left    = $matches[1][$key];
   $operand = $matches[6][$key] ? '!=' : '==';
   $andor   = $matches[6][$key] ? ' AND ' : ' OR ';
   $items   = preg_replace('/(&(amp;)?)+/', '|', $matches[7][$key]);
   $cond    = array();
   foreach (explode('|', $items) AS $right)
   {
    $tmpl   = preg_match('#^(\'|").+\\1$#', $right) ? '%s %s %s' : '%s %s "%s"';
    $cond[] = sprintf($tmpl, $left, $operand, $right);
   }

   // Replace {if foo IN (a|b|c)} with {if foo == 'a' OR foo == 'b' OR foo == 'c'}
   $tagdata = str_replace(
    $match,
    LD.'if '.implode($andor, $cond).RD,
    $tagdata
   );
  }
 }
 return $tagdata;
}
       
Sobral's avatar
Sobral
87 posts
11 years ago
Sobral's avatar Sobral

I know, @Low. I actually use it a lot on SQL queries.

And I know this isn’t an standard. To me, this looks like more when you have consecutive cases on a switch statement. I really don’t know if you should keep only the solutions already available on PHP. The point of a template language is to make the templates easier to code and the IN is easier than several == and OR.

But my ideas are usually not common sense. I have a completely different background then a programmer. I’m a designer first.


If I had to code it, my idea would be to:

category_id IN (1|2|3)

Parse the category_id, split by IN, then add | around both:

  • |4|
  • |1|2|3|

And looks for the position of the first item on the second.

But I’m insane! You’ll probably find this more fun than useful.

       
Derek Jones's avatar
Derek Jones
7,561 posts
11 years ago
Derek Jones's avatar Derek Jones

Not really a productive comment I know, and I can’t even identify fully why, but it sure is weird to me to mimic a SQL comparison operator in a conditional.

Edit: actually I think that’s simply the reason. Conditionals don’t use WHERE, they use programmatic IF, and where would we stop? LEAST(), GREATEST(), COALESCE()?

       
Low's avatar
Low
407 posts
11 years ago
Low's avatar Low

It’s not really about the syntax of the conditional, but more about the problem it solves, Derek. I have a list of values in an outer tag that needs to be used by a conditional in an inner tag. The multiple values are, like EE parameters, separated by pipes, but this doesn’t work:

{if 'lorem' == 'foo|bar|lorem|ipsum'}

Instead, I need to write this:

{if 'lorem' == 'foo' OR 'lorem' == 'bar' OR 'lorem' == 'lorem' OR 'lorem' == 'ipsum'}

However, when you write the template, there’s no way to know all the options. Let alone that it just looks obtuse, too. So I set out to solve the problem by thinking up some way that I would like to write such a conditional, and that it would still be picked up by the native template parser. PHP’s equivalent would be, as I said earlier, in_array(). IN array. So, logically…

{if 'lorem' IN ('foo', 'bar', 'lorem', 'ipsum')}

…made a lot of sense for me.

Still, the main issue here is allowing multivalued vars to be used in conditionals.

       
Brian Litzinger's avatar
Brian Litzinger
711 posts
11 years ago
Brian Litzinger's avatar Brian Litzinger

Throwing this out there, what if it was this:

{if 'lorem' find 'foo|bar|lorem|ipsum'}
{if find 'lorem' in 'foo|bar|lorem|ipsum'}

Also, isn’t there a hidden “feature” in EE right now that lets you do this, but only for logged in member group checking.

{if in_group(1|2|3)}

Which could be changed to

{if 'lorem' in_group('foo|bar|lorem|ipsum')}
       
Sobral's avatar
Sobral
87 posts
11 years ago
Sobral's avatar Sobral

I don’t think it’s a syntax issue. I guess Derek is afraid of what else can come by this door if they open it.

And you shouldn’t mention in_group, Brian. From the Template.php, line 3343:

// Member Group in_group('1') function, Super Secret!  Shhhhh!
  if (preg_match_all("/in_group\(([^\)]+)\)/", $str, $matches))
  {
       
Low's avatar
Low
407 posts
11 years ago
Low's avatar Low
Also, isn’t there a hidden “feature” in EE right now that lets you do this, but only for logged in member group checking.
{if in_group(1|2|3)}

Yes, which throws an error in 2.9 now.

Anyhoo, I think it would be very useful to allow some sort of in_group or in_array or in_whatevs conditional checking.

       
Joelbradbury's avatar
Joelbradbury
1 posts
11 years ago
Joelbradbury's avatar Joelbradbury

I’ve used a similar custom conditional to what @low has in LS for this same issue.

Especially dealing with form state re-populations for arrays of elements, there’s really no other way to do it (without dipping into php in the template).

The specific syntax I settled on was slightly different, but solved the same issue :

{if '{var_one}' exists_in '{piped_list}'} ..

Which is the same conceptually as the example @low provided. Further I extended that to take the next obvious step and allow array to array comparisons, which accounted for reordered elements :

{if '{array_one}' matches '{piped_list}'} ..

and variants for the obvious cases that opens up (is subset, superset, exclusive, etc..). All these would render down to the appropriate EE standard conditionals before parsing.

I’m not saying these extended situations should be handled, but the base example @low provided is a very common one, and does deserve a native solution.

       
Derek Jones's avatar
Derek Jones
7,561 posts
11 years ago
Derek Jones's avatar Derek Jones

No I completely get the reason for wanting it, and even the reasons for settling on the syntax you did. Pascal’s going to be popping in with our proposed solution, and my comment is really only beneficial in the context of a post-mortem, which I’ll not derail this thread with now. I’ll start a new thread after we’ve nailed this one, as I think it’s a very important conversation to have.

       
Low's avatar
Low
407 posts
11 years ago
Low's avatar Low

Sounds good, Derek. Looking forward to see what Pascal comes up with.

       
1 2 3

Reply

Sign In To Reply

ExpressionEngine Home Features Pro Contact Version Support
Learn Docs University Forums
Resources Support Add-Ons Partners Blog
Privacy Terms Trademark Use License

Packet Tide owns and develops ExpressionEngine. © Packet Tide, All Rights Reserved.