-
-
Notifications
You must be signed in to change notification settings - Fork 513
Improve compatibility with third-party plugins through "the_post" action #1639
Description
Now, here’s a problem I encountered when working with ACF and WooCommerce.
Current behavior
When an ACF field returns an array of post objects, Timber tries to convert these WP_Post objects into Timber\Post objects:
Line 735 in 0d72eee
| $value = $this->convert($value, __CLASS__); |
This line was added in #969, solving a compatibility issue with Visual Composer (#958).
There’s one thing though that might cause weird behavior when working with other plugins like WooCommerce. In Timber\Post::get_info, a the_post action is called, where plugins might hook in, e.g. to setup their global data:
Lines 529 to 536 in 0d72eee
| protected function get_info( $pid ) { | |
| $post = $this->prepare_post_info($pid); | |
| if ( !isset($post->post_status) ) { | |
| return null; | |
| } | |
| do_action_ref_array('the_post', array(&$post, &$GLOBALS['wp_query'])); | |
WooCommerce hooks into the the_post to setup its $product global. However, it shouldn’t set that global when we loop over ACF objects. In my case, on a single product view, I have a custom list of products that I show before the related products section. That list of products is defined through ACF. Now when Timber loops over that list, the $product global is updated for each of the items in the list. When WooCommerce displays the related products, it takes the $product global as a reference and derives the related product from it. Unfortunately, this is always the last product of my ACF list. The related products section seems to be displaying random products.
Solutions
My problem is not that I can’t find a solution that works for me. It took a long time to track down what’s going on and I’m looking for a solution that will not cause any headache for other developers and that works out of the box, and I see multiple directions that we could go.
Solution 1: Make ACF filters not call the_post.
We could use a filter to disable the_post hook when ACF filters converts WP_Post objects into Timber\Post objects:
if ( apply_filters( 'timber/post/setup_global', '__return_true' ) ) {
do_action_ref_array( 'the_post', array( &$post, &$GLOBALS['wp_query'] ) );
}Maybe we could also say that the_post shouldn’t be called when Timber\Post::convert() is used.
Solution 2: Add a new integration for Visual Composer.
The new integration could call the_post in the proper place. This would seem like the simplest solution. But on the other hand, the_post is an integral part of WordPress, so we probably should integrate it into Timber as well.
Which leads me to solution 3:
Solution 3: Find a proper place for the_post.
In WordPress, the_post is hooked in setup_postdata(), which is run when $wp_query->the_post() is run. We already do this in the QueryIterator:
Lines 124 to 127 in 0d72eee
| public function current() { | |
| global $post; | |
| $this->_query->the_post(); |
In default WordPress templates, you’d also use the Loop to display a post, so $wp_query->the_post() is called as well. But in Timber, we use new Timber\Post() to setup a post object, so neither $wp_query->the_post() nor setup_postdata() is called.
How could we improve this? I’m asking myself: Would it be enough if the the_post hook would be called only once for singular pages? I think the_post is essential for looping over post objects, so maybe it should be only set there, and not when setting up a Timber\Post object?
Currently, when using Timber::get_context(), Timber sets up the default post query:
Line 245 in 0d72eee
| self::$context_cache['posts'] = new PostQuery(); |
This is useful for archive pages. But shouldn’t Timber do the same for singular templates, with something like this?
// Untested, only theoretical
if ( is_singular() ) {
global $post;
setup_post_data( $post );
$post = new Timber\Post( $post );
self::$context_cache['post'] = $post;
} else {
self::$context_cache['posts'] = new PostQuery();
}This could also lead to simpler template files for simple use cases:
single.twig
<?php
use Timber\Timber;
Timber::render( 'single.twig', Timber::get_context() );What version of WordPress, PHP and Timber are you using?
Timber 1.6.0
How did you install Timber?
Composer