Revolution: recursive transfer of parameters to snippets

Created on 10 May 2017  Â·  48Comments  Â·  Source: modxcms/revolution

example:
Template

<html lang="en">
    [[!getResources? 
        &parents=`10`
        &limit=`0`
        &includeTVs=`sort`
        &includeContent=`1`
        &showUnpublished=`1`
        &tpl=`@INLINE [[+content]]`
    ]]
</html>

Children Resource ID10

<body>
    [[!getResources? 
        &parents=`11`
        &limit=`0`
        &includeTVs=`sort`
        &includeContent=`1`
        &showUnpublished=`1`
        &tpl=`@INLINE [[+content]]`
        &sortby=`{"sort":"ASC"}`
    ]]
</body>

Children Resource ID11
<div>[[*content]]</div>

(this change for the fact-finding purposes - On a substantial scale doesn't solve a problem)
To successfully execute this code, you need to make edits:
File: \core\model\modx\modparser.class.php
Method: processTag
in ~~429 line
replace
$this->processElementTags($outerTag, $innerTag, $processUncacheable);
in

if(preg_match('~^([^\*|\~|%\+|\$][\s\S]*?[\s]+&tpl[\s]*=[\s]*`)([\s\S]*?)(`[\s\S]*)$~',$innerTag,$mat)){


            $innerTag=$mat[1].' {sfrfgfhdgfj} '.$mat[3] ;
            $this->processElementTags($outerTag, $innerTag, $processUncacheable);
            $innerTag=str_replace(' {sfrfgfhdgfj} ',$mat[2 ],$innerTag);
        } else{
            $this->processElementTags($outerTag, $innerTag, $processUncacheable);
        }

And in ~~263 line delete or change in comment.

$processed+= $this->processElementTags( $parentTag, $content, $processUncacheable, $removeUnprocessed, $prefix, $suffix, $tokens, $depth);

P/S : The solution has not been tested globally. It is offered as a further development of the product.
Thanks!

Most helpful comment

I am still not 100% sure what issue it is you are trying to identify here, but I can explain to you why you see the behavior you do. In my opinion, the error you see is a result of a weakness in how the MODX parser works. The MODX parser parses to the deepest tag and the works its way upwards. I might be mistanken, but I've played with the parser and how it works before and this is my understanding of it.

Using the example that triggers an out of memory error in my second test:

[[!getResources?
    &parents=`-1`
    &resources=`9`
    &limit=`0`
    &includeTVs=`sort`
    &includeContent=`1`
    &showUnpublished=`1`
    &tpl=`@INLINE [[+content]]`
]]

When the parser parses this content it runs the snippet (correctly). I am not 100% what the parser does here, but my understanding is that it leaves the expression [[+content]] alone, because no such placeholder exists (yet). The error occurs when the parser then attempts to parse the expression:

[[!getResources?
    &parents=`-1`
    &resources=`10`
    &limit=`0`
    &includeTVs=`sort`
    &includeContent=`1`
    &showUnpublished=`1`
    &tpl=`@INLINE [[+content]]`
]]

When this happens, the parser recognizes the [[+content]] tag, which was defined by the previous iteration the parser made. This results in the following expression:

[[!getResources?
    &parents=`-1`
    &resources=`10`
    &limit=`0`
    &includeTVs=`sort`
    &includeContent=`1`
    &showUnpublished=`1`
    &tpl=`@INLINE Bar
        [[!getResources?
            &parents=`-1`
            &resources=`10`
            &limit=`0`
            &includeTVs=`sort`
            &includeContent=`1`
            &showUnpublished=`1`
            &tpl=`@INLINE [[+content]]`
        ]]`
]]

Now, it is easy to see what the parser would do next; evaluate the [[+content]] tag again. This recursive process continues until the parser runs out of memory.

The reason this happens is because the MODX parser does not know the difference between a placeholder as a part of a template tag, and when it should be parsed and expanded.

This is, in my opinion, expected behavior as it is justified by how the parser is implemented. As far as I can see, there is no way to get around this functionality as the parser exists today.

Is this the problem you mean?

All 48 comments

What happens if you dont change the line of code? What is the error?

@OptimusCrime
Example Error No. 1: Recursion limited 10 entrances
resource
test1 -ID 01
test2 -ID 02

[[ !getResources? &resources=`02` 
    &includeContent=`1`
    &showUnpublished=`1`
    &tpl=`@INLINE [[+content]]`
]]

test2

<div> TEST2 </div>
[[+content]]

modx.test/index.php?id=01
Response
TEST2
TEST2
TEST2
TEST2
TEST2
TEST2
TEST2
TEST2
TEST2
TEST2

Example Error No. 2: The recursion is infinite. Difficult algorithm
test1 -ID 01
test2 -ID 02
test3 -ID 03

[[ !getResources? &resources=`02` 
    &includeContent=`1`
    &showUnpublished=`1`
    &tpl=`@INLINE [[+content]]`
]]


test2

[[ !getResources? &resources=`03` 
    &includeContent=`1`
    &showUnpublished=`1`
    &tpl=`@INLINE [[+content]]`
]]

test3 
[[+content]]    
<div> TEST3 </div>

Request
modx.test/index.php?id=01
Response
Error Recursive

Example Error No. 3: The recursion is infinite. Difficult algorithm

test1 -ID 01
    test2 -ID 02 (children ID 01)
        test3 -ID 03 (children ID 02)



test1

[[ !getResources? &resources=`02`
    &depth=`0`
    &includeContent=`1`
    &showUnpublished=`1`
    &tpl=`@INLINE [[+content]]`
]]


test2

[[ !getResources? &resources=`03` 
    &includeContent=`1`
    &showUnpublished=`1`
    &tpl=`@INLINE [[+content]]`
]]

test3   
<div> TEST3 </div>

Request
modx.test/index.php?id=01
Response
Error Recursive

Perhaps this problem needs to be carried to getResources. But I consider that tags in the templates for Snippets to not have parsing. They will be processed by a snippet as it will be convenient to it.

Example Error No. 4: T
test1 -ID 01
test2 -ID 02(children ID 01)
tesy3 -ID 03(children ID 02)
test1

[[ !getResources? &resources=02
&depth=0
&includeContent=1
&showUnpublished=1
&tpl=@INLINE [[+content]]
]]
test2

[[ !getResources? &resources=03
&depth=0
&includeContent=1
&showUnpublished=1
&tpl=@INLINE TEST
]]
test3
---NO TEXT ---

modx.test/index.php?id=01
The expected result:
TEST
Reality result
TEST TEST

in an example the 3 and 4 problem of a recursion has decided it is banal
it is necessary to exclude a resource in which the this script appears.
To exclude itself.

exemple ID 02
test2
[[ !getResources? &resources=03,-02
&depth=0
&includeContent=1
&showUnpublished=1
&tpl=@INLINE TEST
]]

Example Error No. 5:
test1 -ID 01
test2 -ID 02
test2 -ID 03
Resource

test1

ID01 -[ [[*id]] ]
[[ !getResources?
    &depth=`0`
    &resources=`02,-01` 
    &includeContent=`1`
    &showUnpublished=`1`
    &tpl=`@INLINE [[+content]]`
]]

test2

ID02 -[ [[+id]] ]
[[ !getResources?
    &depth=`0`
    &resources=`03,-02` 
    &includeContent=`1`
    &showUnpublished=`1`
    &tpl=`@INLINE [[+content]]`
]]

test3
`ID03 -[ [[+id]] ]

`
The expected result:
ID01 -[ 01 ] ID02-[ 02 ]; ID03-[ 03 ];
Reality result
ID01 -[ 01 ] ID02-[ 02 ]; ID02-[ 03 ]; ID03-[ 03 ];

Can you try doing it without the inline template? I am not sure just how this should work, but you're purposely triggering recursion here where it seems like the parser it not honoring the "namespaces" of deeper iterations.

Initially
An empty template is selected for all resources.

Examples based on chunks.
Example Error No. 6:
test1 ID21
test2 ID22
chunk test

test1

ID21 -[ [[*id]] ]
[[ !getResources?
    &depth=`0`
    &resources=`22,-21` 
    &includeContent=`1`
    &showUnpublished=`1`
    &tpl=`@INLINE [[+content]]!`
]]

test2

test1 ID22
ID22-[ [[+id]] ];
[[ !getResources?
&depth=`0`
&resources=`23,-22` 
&includeContent=`1`
&showUnpublished=`1`
&tpl=`test`
]]

chunk test

ID FOR CHUNK  - [[+id]]
[[+content]]

The expected result:
ID21 -[ 21 ] ID22-[ 22 ];ID FOR CHUNK - 23
Reality result
Recursive error

Example Error No. 7:

test1 ID21
test2 ID22
test2 ID23
chunk test

test1 ID21

ID21 -[ [[*id]] ]
[[ !getResources?
    &depth=`0`
    &resources=`22,-21` 
    &includeContent=`1`
    &showUnpublished=`1`
    &tpl=`@INLINE [[+content]]!`
]]

test1 ID22

ID22-[ [[+id]] ];
[[ !getResources?
&depth=`0`
&resources=`23,-22` 
&includeContent=`1`
&showUnpublished=`1`
&tpl=`@INLINE [[+content]]`
]]

test1 ID23

ID23-[ [[+id]] ];
[[ !getResources?
&depth=`0`
&resources=`24,-23` 
&includeContent=`1`
&showUnpublished=`1`
&tpl=`test`
]]

chunk test

ID FOR CHUNK  - [[+id]]

The expected result:
(if resource ID 24 not created)
ID21 -[ 21 ] ID22-[ 22 ]; ID23-[ 23 ]; ID FOR CHUNK -
(if resource ID 24 created)
ID21 -[ 21 ] ID22-[ 22 ]; ID22-[ 23 ]; ID23-[ 23 ]; ID FOR CHUNK - 24
Reality result
(if resource ID 24 not created)
ID21 -[ 21 ] ID22-[ 22 ]; ID22-[ 23 ]; ID23-[ 23 ]; ID FOR CHUNK - 22
(if resource ID 24 created)
ID21 -[ 21 ] ID22-[ 22 ]; ID22-[ 23 ]; ID23-[ 23 ]; ID FOR CHUNK - 24 ID FOR CHUNK - 22

@INLINE and CHUNK work on the same principle.
No role will play if we define instead of @INLINE name CHUNK

In the call code
@INLINE [[+ content]]
Is tantamount to
 $ Output = $ chunk-> process ($ properties, '[[+ content]] `);

Have you tried this with pdoResources? I'm not 100% sure exactly what the problem is here, but pdoResources does a lot of thing differently than getResources does.

@sottwell
I analyzed the code getResources - and there are no problems in it!
The whole problem goes deeper into the process () method and so on.
I'll assume that somewhere in a recursive occurrence one common variable is used (passing by reference), for all recursive loops, which must be declared separately for each cycle!

I work 2 days for you to collect examples and give food for thought! So do not be too lazy to try the examples and fix the problem.

This is way out of my league, I just wondered if pdoTools/pdoResources might not have the same problem.

I have a hard time understanding the issue here. But try calling the snippet in the resource without [[! and only use &includeContent=1 here and not in template

@mrhaw - the philosophy of a code is that one resource loads other resources which in turn too load resources. Therefore all resources are joined in one whole.
Examples just also describe it.
But where that in a code occurs a mistake and a code will be flown down not correctly and arises or the infinite recursion or data is displayed with an excessive code in a template.

pdoResources
Example Error No. 7:
Resource
test21 ID21
test22 ID22
test23 ID23
test24 ID24

test21

ID21 -[ [[*id]] ]
[[ !pdoResources?
    &parents=`0`
    &resources=`22` 
    &includeContent=`1`
    &showUnpublished=`1`
    &tpl=`@INLINE [[+content]]`
]]

test22

ID22-[ [[+id]] ];
[[ !pdoResources?
    &parents=`0`
    &resources=`23` 
    &includeContent=`1`
    &showUnpublished=`1`
    &tpl=`@INLINE [[+content]]`
]]

test23

ID23-[ [[+id]] ];
[[ !pdoResources?
&parents=0
&resources=24
&includeContent=1
&showUnpublished=1
&tpl=@INLINE [[+content]]
]]

test24

ID24  -[ [[+id]] ]

request/index.php?id=21
The expected result:
ID21 -[ 21 ] ID22-[ 22 ]; ID23-[ 23 ]; ID24 -[ 24 ]
Reality result
ID21 -[ 21 ] ID22-[ 22 ]; ID22-[ 23 ]; ID23-[ 23 ]; ID23-[ 24 ]; ID24 -[ 24 ]

pdoResources
Example Error No.8: (change Exemple â„–7)
Change only
test24

ID24  -[ [[+id]] ]
[[+content]]

request/index.php?id=21

The expected result:
ID21 -[ 21 ] ID22-[ 22 ]; ID23-[ 23 ]; ID24 -[ 24 ]
Reality result:

recursive error

I'm sorry, I don't see an error here. In each level, you are telling it &parents=0, with a different &resources in each level. &resources is an AND, not a filter. So for each level you will get all resources with a parent of 0, plus the specified resource (in this case redundant).
Comma-separated list of resources to add to the results. If the id of the resource starts with a hyphen , this resource is excluded from the query.

@sottwell Run the code
Create 4 resources. In resources (ID2 ID3 ID4) place the script [[+ id]]
In resource ID1 place the script

[[ !pdoResources?
&parents=`0`
&resources=`01`
&includeContent=`1`
&showUnpublished=`1`
&tpl=`@INLINE  NULLLLL`]]

You will see what resources it loads

In general, the problem is devoted for developers MODX. If necessary, they will understand, and the examples will be scrutinized.

I think I understand where you're getting but I'm not sure of the complete scope of this.

I have the following resources:

- Foo (8)
    - Bar (9)
        - Baz (10)

They all have the empty template.

Test 1

Foo:

[[!getResources?
    &parents=`-1`
    &resources=`9`
    &limit=`0`
    &includeTVs=`sort`
    &includeContent=`1`
    &showUnpublished=`1`
    &tpl=`test`
]]

Bar:

[[!getResources?
    &parents=`-1`
    &resources=`10`
    &limit=`0`
    &includeTVs=`sort`
    &includeContent=`1`
    &showUnpublished=`1`
    &tpl=`test`
]]

Baz:


And the chunk test:

[[+content]]

The works as expected:

Foo Bar Baz

Test 2

Foo:

[[!getResources?
    &parents=`-1`
    &resources=`9`
    &limit=`0`
    &includeTVs=`sort`
    &includeContent=`1`
    &showUnpublished=`1`
    &tpl=`@INLINE [[+content]]`
]]

Bar:

[[!getResources?
    &parents=`-1`
    &resources=`10`
    &limit=`0`
    &includeTVs=`sort`
    &includeContent=`1`
    &showUnpublished=`1`
    &tpl=`@INLINE [[+content]]`
]]

Baz:


This results in an fatal error:

Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 20480 bytes) in .../revolution/core/model/modx/modparser.class.php on line 172

Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 20480 bytes) in .../revolution/core/xpdo/om/xpdoobject.class.php on line 236

Is this (and related problems) the ones you are trying to pinpoint?

@OptimusCrime
Thank you! Itself came to exactly this conclusion using pdoResources.
But there is a complication:
In the example with the chunk "test", in the "Baz" file write [[+ content]]
It will be an interesting result.
Or
in Baz write [[$test]] as an alternative to [[+ content]].
The result is better. But not what was expected.

[[+ Content]] in the last file is needed in different cases. For example - the file is downloaded directly, or it is loaded through the call stack in the examples of which we analyzed.

I am still not 100% sure what issue it is you are trying to identify here, but I can explain to you why you see the behavior you do. In my opinion, the error you see is a result of a weakness in how the MODX parser works. The MODX parser parses to the deepest tag and the works its way upwards. I might be mistanken, but I've played with the parser and how it works before and this is my understanding of it.

Using the example that triggers an out of memory error in my second test:

[[!getResources?
    &parents=`-1`
    &resources=`9`
    &limit=`0`
    &includeTVs=`sort`
    &includeContent=`1`
    &showUnpublished=`1`
    &tpl=`@INLINE [[+content]]`
]]

When the parser parses this content it runs the snippet (correctly). I am not 100% what the parser does here, but my understanding is that it leaves the expression [[+content]] alone, because no such placeholder exists (yet). The error occurs when the parser then attempts to parse the expression:

[[!getResources?
    &parents=`-1`
    &resources=`10`
    &limit=`0`
    &includeTVs=`sort`
    &includeContent=`1`
    &showUnpublished=`1`
    &tpl=`@INLINE [[+content]]`
]]

When this happens, the parser recognizes the [[+content]] tag, which was defined by the previous iteration the parser made. This results in the following expression:

[[!getResources?
    &parents=`-1`
    &resources=`10`
    &limit=`0`
    &includeTVs=`sort`
    &includeContent=`1`
    &showUnpublished=`1`
    &tpl=`@INLINE Bar
        [[!getResources?
            &parents=`-1`
            &resources=`10`
            &limit=`0`
            &includeTVs=`sort`
            &includeContent=`1`
            &showUnpublished=`1`
            &tpl=`@INLINE [[+content]]`
        ]]`
]]

Now, it is easy to see what the parser would do next; evaluate the [[+content]] tag again. This recursive process continues until the parser runs out of memory.

The reason this happens is because the MODX parser does not know the difference between a placeholder as a part of a template tag, and when it should be parsed and expanded.

This is, in my opinion, expected behavior as it is justified by how the parser is implemented. As far as I can see, there is no way to get around this functionality as the parser exists today.

Is this the problem you mean?

I can suggest a way to work around this problem, but it might lead to breaking changes, so there is no way to introduce such features in the 2.x branch. If the expression could be formulated as:

[[!getResources?
    &parents=`-1`
    &resources=`10`
    &limit=`0`
    &includeTVs=`sort`
    &includeContent=`1`
    &showUnpublished=`1`
    &tpl=`@INLINE [[\+content]]`  # <- See change
]]

We could implement a way for scriptproperties in MODX to escape such tags. This could make it possible for a MODX parser to avoid handing tags when used as a part of an expression that should not be evaluated at parse time.

What do you think?

Amazing post @OptimusCrime I vote for no change.

It's to me bad practice constructing a MODX website this way. I would be frustrated if I was handed over a site setup this way.

When it comes to the "programming philosophy" this can be satisfied by writing a custom snippet!?
» $modx->getOption('parser_max_iterations', null, 10);
http://stackoverflow.com/questions/25717902/modx-revo-ajax-call-dont-execute-snippet-inside-chunk/25738004#25738004

@OptimusCrime
The example with INLINE in you goes to infinite recursion. I have a similar example of the result. (See example # 5.)
Perhaps you have somewhere the template is not empty.

@OptimusCrime
When making a decision or developing code, you need to understand that recursion should be perfectly constructed. (Without hacks) Otherwise there is a great chance that the recursion will go to infinity or the result will not be correct. It's like a monkey with a grenade - you do not know where and when it BOOM! Not correct recursion can be a significant constraint for the growth of the MODX project.

@OptimusCrime
Hack with chunk

Chunk 'content'

[[+content]]

in resources

[[.....
&tpl=[[@INLINE <div>[[$content]]</div> ]] 

Hack code:
If you work with other fields(Dynamically determined) then the script has a new syntax.

! { +content }!
! { +field }!

For this, in the getResources snippet at the beginning of the code,

 $tmpls=['tpl','tplOdd','tplFirst','tplLast','conditionalTpls'];
foreach($scriptProperties as $k=> &$v ){
     if(in_array($k,$tmpls) or preg_match('~^tpl_[n]{0,1}[\d]+$~',$k)){
        $v=str_replace(['}!','!{'],[']]','[['],$v); 
        $vv=[$k=>&$v];
        extract($vv);
     }

 } 

Call

[[.....
&tpl=[[@INLINE <div>!{+content}! !{+field}!</div> ]] 

Or create new Snippet from This code

So to say, "closed the eyes of the parser", so that it does not disassemble the script. Because this script will parse the embedded getResources (Further on a recursion)

But the problem remained if to add in the last resource (the final resource) [[+ content]]

in pdoTools, there is this syntax for INLINE tpl-chunks:

&tpl=`@INLINE {{+content}}`

this is just a simple str_replace, before parsing the chunk. Isn't it this, what you need?

@Bruno17 Thank !
Your option as an alternative will be interesting!

@Bruno17 At the moment I'm considering the situation deeper to solve the main defect of such behavior.

first problem is that there are tags in the context that loop the script .
To exclude this problem, the tags that are passed to the snippet as a plain text should be camouflaged,
  Within the existing context they will not then be transformed.
For example: camouflage-create a new chunk.

Second problem: But the problem remained if to add in the last resource (the final resource) [[+ content]].
The script is included in the recursion which is limited to ten iterations.
This occurs at stage 2 of a recursive call in order to test new tags.
emulation for exemple:

    $content='bla bla [[+content]]';
            $code=['[[+content]]','new [[+content]]'];
            while (strpos($content,$code[0])){
                $content=str_replace($code[0],$code[1],$content);
            }

If we approach the solution of the problem primitively, then we must exclude from the context a tag that loop itself.
For an example solution:
In the file core model modx modparser.class.php
In the processElementTagsmethod
 (~ 264 line)
Replace

$ This-> mergeTagOutput ($ tagMap, $ content);
``
To the code

foreach($tagMap as $k=>&$v){
$v=str_replace($k,' ',$v);
}
$this->mergeTagOutput($tagMap, $content);
```

[...]
To exclude this problem, the tags that are passed to the snippet as a plain text should be camouflaged,
[...]

Like I already state, this is not possible. The MODX parser has no sense of context and does not know if a placeholder is located as such:

[[!snippet? 
    &foo=`[[+bar]]`
]]

Or

[[!snippet? 
    &foo=`bar`
]]
[[+baz]]

This is just how the parser was designed; working from a bottom-up-approach (which I think is pretty common for parsers like this (?)). This means that taking "shortcuts" such as inline placeholders may blow up in your face. The alternative syntax featured in pdoTools is obviously an attempt at solving these shortcomings.

On one point do I agree however. There should be a maximum depth the parser goes to, to avoid recursive errors like the ones I've reported. Reason the max_depth functionality does not kick in is because this limit only affects one parser run. This means that each time the parser sees a placeholder it recursively expands (like [[+content]]) it will set a maximum depth for recursion on that tag, but that maximum depth is not reused when the parser finds another placeholder to expand.

Introducing a ultimate_max_depth option could eliminate the fatal errors at least.

By the way, the changes you suggest will because major breaking changes. This is supported syntax today, that some people rely on:

[[foo-[[+place[[+et]]]]

If the value of [[+et]] is "holder" we expand the expression to this:

[[foo-[[+placeholder]]

And if the value of [[+placeholder]] is "bar", we expand further to:

[[foo-bar]]

Because the parser does not care whee the location of opening and closing tags are, there is no way to attempt to make a context out of this, as there are way too many edge cases to evaluate.

@OptimusCrime It's enough to know the problem of its cause in the order to find solutions. Consider as a development perspective.
If initially there would not be this problem, then MODX would have more alternatives for solving projects .

To exclude this problem, the tags that are passed to the snippet as a plain text should be camouflaged.

Variant of camouflage described

Hack with chunk

Chunk 'content'

[[+content]]

in resources

[[.....
&tpl=[[@INLINE text [[$content]] text ]]

I don't see any solutions to this problem. The problem is caused by extras supporting inline placeholders, a strategy we KNOW will not work if the placeholder is defined. There is no way to get around this.

@OptimusCrime Everything is solved. Just not everyone wants to decide

@OptimusCrime Dig to the side of $ this-> mergeTagOutput ($ tagMap, $ content);

I don't think we got anywhere with this. Closing?

Yep, closing. With all respect to people who took part in such a long discussion and investigation, but we don't want to break backward compatibility in the parser, even in 3.x. I hope in the future it will be something like Twig, but not today.

Was this page helpful?
0 / 5 - 0 ratings