Skip to content

Improve compatibility with third-party plugins through "the_post" action #1639

@gchtr

Description

@gchtr

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:

$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:

timber/lib/Post.php

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:

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:

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

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions