Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FSE - infinity loop when search_template_hierarchy is empty #31652

Closed
Chrico opened this issue May 10, 2021 · 4 comments · Fixed by #31671
Closed

FSE - infinity loop when search_template_hierarchy is empty #31652

Chrico opened this issue May 10, 2021 · 4 comments · Fixed by #31671
Assignees
Labels
[Status] In Progress Tracking issues with work in progress

Comments

@Chrico
Copy link
Contributor

Chrico commented May 10, 2021

FSE causes following error:

Xdebug has detected a possible infinite loop, and aborted your script with a stack depth of '256' frames

with following Stack:

#85 /var/www/wp-content/plugins/gutenberg/lib/full-site-editing/template-loader.php(48): get_search_template()
#86 /var/www/wp-content/plugins/gutenberg/lib/full-site-editing/template-loader.php(152): get_template_hierarchy('search')
#87 /var/www/wp-content/plugins/gutenberg/lib/full-site-editing/template-loader.php(66): gutenberg_resolve_template('search', Array)
#88 /var/www/wp-includes/class-wp-hook.php(292): gutenberg_override_query_template('', 'search', Array)
#89 /var/www/wp-includes/plugin.php(212): WP_Hook->apply_filters('', Array)
#90 /var/www/wp-includes/template.php(63): apply_filters('search_template', '', 'search', Array)
#91 /var/www/wp-includes/template.php(471): get_query_template('search')

Description

Our Plugin hooks into the search and replaces the template. As "micro"-optimization we added following code:

add_filter('search_template_hierarchy', '__return_empty_array', PHP_INT_MAX);

This will clean up the template hierarchy and skips a few file_exists-call from WordPress core, because we just want 1 single search template which we know it exists.

Following problem occurs:

  1. Gutenberg hooks into search_template
  2. which tries to resolve in gutenberg_override_query_template() the $current_template.
  3. when the $template_hierachy is empty, Gutenberg tries to call get_template_hierarchy( $template_type )
  4. which calls afterwards get_search_template(),
  5. which calls internally get_query_template()
  6. which finally gets resetted again by our filter above to an empty array.
  7. And last but not least... in get_query_template() the search_template-filter (see 1.) is applied again.

An infinity loop is born. :)

WordPress information

  • WordPress version: 5.7.1
  • Gutenberg version: 10.5.4
  • Are all plugins except Gutenberg deactivated? yes (expect the Plugin with a single filter from above)
  • Are you using a default theme (e.g. Twenty Twenty-One)? yes
@gmazzap
Copy link

gmazzap commented May 10, 2021

I think the fundamental problem is in core.

WordPress never had a representation in code of its template hierarchy. The hierarchy has in fact a "graphical representation" without a correspondent code representation.

In the years, I approached that issue several times (e.g. here, a Composer package with over a 1.1 millions downloads).

The logic in WordPress template-loader.php can not be called programmatically: that is an ancient piece of procedural code.

If WordPress would have a code representation of its template hierarchy, Gutenberg would be able to use that to determine which full-page template to use, instead of "hacking" WordPress and cause issues like the one in OP.

I have the feeling that the issue caused by technical debt in WordPres code (that procedural procedure in template-loader.php), is approached by Gutenberg by throwing some more technical debt at it.

Would not be better to address the issue in core, have a proper API around template hierarchy, and then make use of it in Gutenberg?

That would be entirely doable in 100% backward compatible way, and would improve both WordPress and Gutenberg code.

@ockham
Copy link
Contributor

ockham commented May 10, 2021

@gmazzap It's true that we've introduced get_template_hierarchy() in order to have a function that returns the template hierarchy by tapping into Core's existing template resolution logic (via the {type}_template_hierarchy filter).

FWIW, it's on the roadmap for WP 5.8 to move GB's block template resolution logic into Core, see e.g. this conversation.


Looking for a more targeted solution for the present issue, I had another look at the function signatures of gutenberg_override_query_template(), get_template_hierarchy(), and friends. In gutenberg_override_query_template(), we allow for a missing $templates argument (in which case it defaults to an empty array), which I think was previously needed (when we were calling gutenberg_override_query_template() from another callsite that couldn't provide the template hiearchy as an argument) -- but I don't think we need it anymore!

So we might make the third argument in gutenberg_override_query_template non-optional (since it's only ever hooked into the {type}_template filter, which always provides that third argument. This might allow for a different handling of the empty-array case -- one that doesn't lead to an infinite loop 🤔

@github-actions github-actions bot added the [Status] In Progress Tracking issues with work in progress label May 10, 2021
@ockham
Copy link
Contributor

ockham commented May 10, 2021

Draft PR: #31671.

@gmazzap
Copy link

gmazzap commented May 11, 2021

It's true that we've introduced get_template_hierarchy() in order to have a function that returns the template hierarchy by tapping into Core's existing template resolution logic (via the {type}_template_hierarchy filter).

I saw that, and that's IMO the problem. If something also listens to the same hooks using a later priority, what you store is not what people expect. Moreover, someone might have complex logic on {type}_template filters instead to determine the proper template, and so only using {type}_template_hierarchy ignores all the logic in {type}_template.

I think that what is needed in the core is a set of get_{type}_hierarchy functions with the same logic of get_{type}_template but instead of "locating" a single template would return the hierarchy.
For example, a function like get_page_hierachy would return an array similar to:

[
   'some-page-template.php',
   'page-some-slug.php',
   'page-123.php',
   'page.php',
]

at that point, the existing get_page_template could become something like this:

function get_page_template() {
   return get_query_template( 'page', get_page_hierarchy(), false );
}

You see that I passed false as 3rd argument to get_query_template something that is not supported right now.
The reason is that get_{type}_hierarchy functions should fire the filters "{$type}_template_hierarchy" to resolve the hierarchy, but that filter is fired in get_query_template.

By supporting a boolean 3rd argument for get_query_template, which would default to true for backward compatibility, it would be possible to don't apply the "{$type}_template_hierarchy" again if it is false. That would ensure 100% backward compatibility.

Having that in the core, WordPress would not need to change anything else, considering that all the get_{type}_template would continue to work as expected.

But Gutenberg could then write a function that making use of the new get_{type}_hierarchy functions, given a page query, could obtain an array like:

[
   'some-page-template',
   'page-some-slug',
   'page-123',
   'page',
   'singular',
]

and use that to resolve the FSE template to use, instead of "hacking" with WP hooks.

Maybe, after an FSE template would found, it could be passed through to the {type}_template filter to also respect any logic in there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Status] In Progress Tracking issues with work in progress
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants