Search code examples
wordpressguzzlewordpress-rest-apiwpml

Can't display custom field from Wordpress REST API custom endpoint


I'm using the Wordpress Rest API to import content from a Wordpress website into a PHP application. It's nothing complex, just a main page with the list of posts and the pages for individual posts.

I added some fields to the API response, in particular one to get the url of the first image inserted in the post.

This is the code for this part:

add_action('rest_api_init', function () {
    register_rest_field('post', 'post_images', array(
        'get_callback'    => 'get_first_image',
        'update_callback' => null,
        'schema'          => null
    ));
});

function get_first_image($obj, $name, $request)
{
    $images = get_attached_media('image', $obj['id']);
    $imagesArray = (array) $images;
    reset($imagesArray);
    $firstImageId = current($imagesArray)->ID;
    $imageSrc = wp_get_attachment_image_url($firstImageId);
    return $imageSrc;
}

It works fine when I'm listing posts in the main page, but from the individual post page the field is empty. The only explanation I can come up with for this is that I have this custom endpoint for the single posts:

function post_by_slug(WP_REST_Request $request)
{
    $postSlug = $request->get_param('post_slug');
    $lang     = $request->get_param('my_lang');
    $myPost   = get_page_by_path($postSlug, OBJECT, 'post');
    $targetPostId   = apply_filters('wpml_object_id', $myPost->ID, 'post',
        false, $lang);
    $targetPost     = get_post($targetPostId);
    $postController = new \WP_REST_Posts_Controller($targetPost->post_type);
    $response       = $postController->prepare_item_for_response($targetPost,
        $request);

    return rest_ensure_response($response);
}

add_action('rest_api_init', function () {
    register_rest_route('pc/v1',
        "/post-slug/(?P<post_slug>\S+)/(?P<my_lang>\w+)", [
            'methods'  => 'GET',
            'callback' => 'post_by_slug',
            'args'     => [
                'post_slug' => 'required',
                'my_lang'   => 'required'
            ]
        ]);
});

From my app, I call it this way:

$client = new Client([
    'base_uri' => 'http://example.com/wp-json/pc/v1/',
    'headers' => [
        'Content-Type' => 'application/json',
        "Accept" => "application/json",
    ],
    'verify' => false,
]);

var_dump(json_decode($client->get("post-slug/$slug/$lang")
                             ->getBody()->getContents()));

What's strange is that accessing the same endpoint directly from the browser I can see all the fields correctly. Am I missing something ovious?


Solution

  • Just answering my own question because I've found what was causing the endpoint to work correctly from my browser but not when accessed through Guzzle.

    The problem was that I was logged in as an admin, so the WPML plugin that I'm using to manage multiple languages on the website was setting this cookie:

    wp-wpml_current_admin_language_d41d8cd98f00b204e9800998ecf8427e:"de"
    

    So the problem was related to this plugin, which I'm actually using in the post_by_slug function. Somehow, specifying the language as I do here is not enough, or just serves a different purpose, not sure:

    $targetPostId   = apply_filters('wpml_object_id', $myPost->ID, 'post', false, $lang);
    

    There are two solutions I could find:

    1) explicitly setting the languge using the plugin switch_lang() method:

    function post_by_slug(WP_REST_Request $request) {
      global $sitepress;
      $postSlug = $request->get_param('post_slug');
      $lang = $request->get_param('my_lang');
      $sitepress->switch_lang($lang);
    

    2) change the guzzle GET request to pass the language as a query parameter, which seems to automatically work with the plugin:

    $response = $client->get("post-slug/$slug", ['query' => ['lang' => $lang]])->getBody()->getContents();
    

    I realize that this is a very specific problem, but maybe it will be of help to someone else.