Passing a post ID to a Twig/Timber function

Viewed 6563

How do I pass a post ID to a Twig/Timber function like edit_post_link?

Reading the docs at https://timber.github.io/docs/guides/functions/#function-with-arguments

A function like edit_post_link will try to guess the ID of the post you want to edit from the current post in The Loop. the same function requires some modification in a file like archive.twig or index.twig. There, you will need to explicitly pass the post ID.

And that is what happens; when I use this

{{ function('edit_post_link', 'Edit', '<span class="edit-link">', '</span>', post.ID) }}

in index.twig, all the edit links have the post ID of the page that displays the loop of custom post types, not the post ID of each custom post type that is in the loop.

I'm using the function below in functions.php, which also forces a target="_blank" on edit links:

add_filter( 'edit_post_link', 'newwindow_edit_post_link', 10, 3 );

global $post;
$post_id = $post->ID;

    function newwindow_edit_post_link( $link, $post_id, $text ) {
        if( !is_admin() )
            $link = str_replace( '<a ', '<a target="_blank" ', $link );
        return $link;
    }

This is the basic loop on index.twig. "people" is a standard WordPress custom post type:

 {% if people %}

            {% for person in people %}

                    <a href="{{ person.link }}">{{ person.name }}</a>

                        {{ function('edit_post_link', 'Edit', '<span class="edit-link">', '</span>', post.ID) }}

            {% endfor %}

    {% else %}

 {% endif %}

That results in all of the edit links pointing to that page, not each custom post type "person."

So how do I call the post ID? Do I need to call the post ID in the custom Post Type function?

The main index.php file has standard Twig functions:

$context = Timber::get_context();
$context['posts'] = Timber::get_posts();
$templates = array( 'index.twig' );
Timber::render( $templates, $context );
3 Answers

Looking at the Twig 2.x documentation there is no {{ function }} Twig function by default. I've certainly never seen this in my years of using Symfony so I suspect this is something custom?

I've just Googled "timber/twig" and this is in fact a WordPress plug-in to provide Twig functionality on your theme templates, therefore I believe you've put the Symfony tag on your question by mistake. I'd suggest removing this and adding wordpress instead so you can attract answers more useful than mine.


We would need to see the PHP source for your custom edit_post_link Twig function for assurance. However it would appear that you simply need to map in your arguments the same order both on the PHP side and Twig side. For example if your function is:

function edit_post_link(string $label, string $openingHtml, string $closingHtml, int $postId) {
    // blah blah blah
}

After you've registered this function with Twig (although Timber appears to claim you may not need to do this, do check) you really would then use it exactly as you wrote:

{{ function('edit_post_link', 'Edit', '<span class="edit-link">', '</span>', post.ID) }}

I sense this may not be what you're getting at though, possibly you're wondering how you grab hold of post.ID in the first place. If that's the case then your issue isn't about {{ function }}, and we'd need to see more of your Twig template source along with the variables you've exposed to it from PHP.

So how do I call the post ID?

If the people in the loop in your index.twig template is an array of posts (i.e. each post is a WP_Post / Timber\Post instance), then you can (or should be able to) retrieve the post ID via person.ID or person.id (yes, both are actually set). So these worked well for me:

{{ function('edit_post_link', 'Edit', '<span class="edit-link">', '</span>', person.id) }}
{{ function('edit_post_link', 'Edit', '<span class="edit-link">', '</span>', person.ID) }}

How I confirmed the above

  1. I installed and activated the official Timber starter theme.

  2. I created front-page.php:

    <?php
    $context = Timber::get_context();
    
    // Here, I defined the `people`.
    $context['people'] = Timber::get_posts( [
        'post_type'      => 'post', // yours would be 'person' and not 'post'
        'posts_per_page' => 3,
    ] );
    
    // This I used for testing only.
    $context['post'] = new Timber\Post();
    
    $templates = array( 'front-page.twig' );
    Timber::render( $templates, $context );
    
  3. Then I created templates/front-page.twig:

    {% extends "base.twig" %}
    
    {% block content %}
        <h2>The queried page's title: {{ post.title }}</h2>
        <p>The queried page's ID: <b>{{ post.id }}</b></p>
        {% if people %}
    
            {% for person in people %}
    
            <a href="{{ person.link }}">{{ person.name }}</a>
    
            {{ function('edit_post_link', 'Edit', '<span class="edit-link">', '</span>', person.id) }}<br>
    
            {% endfor %}
    
        {% else %}
    
        {% endif %}
    
        {% include 'partial/pagination.twig' with { pagination: posts.pagination({show_all: false, mid_size: 3, end_size: 2}) } %}
    {% endblock %}
    

And everything worked just fine for me — the edit_post_link() got called properly and displays the post link with the target="_blank" in the markup. (I put the newwindow_edit_post_link stuff in functions.php)

This is ugly, but if you can't get the edit_post_link function to work in a template.twig, and {{ person.id }} does work, you could use this setup in your twig template.

It determines if a user is logged in and can edit and if so, displays an edit link - dynamic with {{ person.id }} - that opens in a new tab:

{% if user %}
<a class="style-me" target="_blank"
href="{{ site.url }}/wp-admin/post.php?post={{ person.id }}&action=edit">Edit</a>
{% endif %}
Related