1

I built a custom endpoint for the WordPress REST API to get a post revision. The result is in the Wordpress Post syntax:

[ID] => 478
[post_author] => 1
[post_date] => 2017-11-20 17:22:11
[post_date_gmt] => 2017-11-20 16:22:11
[post_content] => My Post content

Whereas the Wordpress REST API would provide me something like this:

"author": 1,
"title": {
    "rendered": "My title"
},
"content": {
    "rendered": "",
    "protected": false
},

(one is printed via php the other is in a JSON format, but what is important is that in the first example it says: post_content and in the second it's content which is then separated in rendered and protected for example.

I am pretty sure that it's almost the same thing as described here: https://wordpress.stackexchange.com/questions/236249/wp-api-v2-custom-endpoint-response-formatting?newreg=7edb54e1ae494e528e5e146982469664

But in my case I have revisions.

I tried to prepare the Post Object for the REST API. I created a new instance of the WP_REST_Revisions_Controller and tried to use its method prepare_item_for_response. $request ist a WP_Rest_Request. (btw: why do I have to write new \WP_REST_Revisions_Controller with a backslash \ before).

$latest_revision = wp_get_post_revisions( $id, $args ); //WP Post Object

$postController = new \WP_REST_Revisions_Controller('revision');
$response = $postController->prepare_item_for_response( $latest_revision, $request );
print_r($response);

The problem is that I get Notices:

<b>Notice</b>:  Trying to get property of non-object in <b>/Users/USER/Documents/my-api/wp/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php</b> on line <b>350</b>

<b>Notice</b>:  Trying to get property of non-object in <b>/Users/USER/Documents/my-api/wp/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php</b> on line <b>354</b>

<b>Notice</b>:  Trying to get property of non-object in <b>/Users/USER/Documents/my-api/wp/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php</b> on line <b>354</b>
....    

Which must refer to these lines: https://developer.wordpress.org/reference/classes/wp_rest_revisions_controller/ (line 350 and following).

After the notices I print the $response and I get this:

WP_REST_Response Object
(
    [links:protected] => Array
        (
        )

    [matched_route:protected] => 
    [matched_handler:protected] => 
    [data] => Array
        (
            [author] => 0
            [date] => 
            [date_gmt] => 
            [id] => 
            [modified] => 
            [modified_gmt] => 
            [parent] => 0
            [slug] => 
            [guid] => Array
                (
                    [rendered] => 
                    [raw] => 
                )

        )

    [headers] => Array
        (
        )

    [status] => 200
)

.. somehow the data is missing or something else went wrong.

And here is the whole php script to see what I am doing:

<?php

/**
 * Add a new API route for a post or pages preview
 */

class Preview_To_REST_API_Controller extends WP_REST_Controller {

    //The namespace and version for the REST SERVER
    var $namespace = 'previews/v';
    var $version   = '1';

    public function register_routes() {
        $namespace = $this->namespace . $this->version;
        $base      = 'preview';
        register_rest_route( $namespace, '/' . $base, array(
            array(
                    'methods'         => WP_REST_Server::READABLE,
                    'callback'        => array( $this, 'get_preview' ),
                    'permission_callback'   => array( $this, 'get_permission' )
                )
        )  );
    }

    // Register our REST Server
    public function hook_rest_server(){
        add_action( 'rest_api_init', array( $this, 'register_routes' ) );
    }

    public function get_permission(){
        if ( ! current_user_can( 'edit_posts' ) ) {
            return new WP_Error( 'rest_forbidden', esc_html__( 'You do not have permissions to view this data.', 'my-text-domain' ), array( 'status' => 401 ) );
        }

        // This approach blocks the endpoint operation. You could alternatively do this by an un-blocking approach, by returning false here and changing the permissions check.
        return true;
    }

    public function get_preview( WP_REST_Request $request ){
        // use the helper methods to get the parameters
        $id = $request->get_param( 'id' );

        // Only return the newest
        $args = array (
            'order' => 'DESC',
            'orderby' => 'date',
            'posts_per_page' => '1'
        );

        // Preview version is saved in the latest revision of the page/post
        $latest_revision = wp_get_post_revisions( $id, $args );

        print_r($latest_revision);

        $postController = new \WP_REST_Revisions_Controller('revision');
        $response = $postController->prepare_item_for_response( $latest_revision, $request );
        print_r($response);

        if ($latest_revision) {
            // Use the current method to get the only element in the revisions array
            // [0] does not return anything, because the Array's value is saved
            // as the ID key: Array[$id], but this ID we need to find out first
            $revision_id = current($latest_revision) -> ID;
            $acf_fields = get_fields($revision_id);

            if ( empty($latest_revision) ) {
                return null;
            }

            // Add acf fields to latest revision
            $latest_revision[$revision_id] -> acf = $acf_fields;

            return $latest_revision;

        } else {
            return null;
        }
    }
}

$preview_controller = new Preview_To_REST_API_Controller();
$preview_controller->hook_rest_server();

I would be really glad to receive any hints for solving this problem. Cheers

Merc
  • 4,241
  • 8
  • 52
  • 81

2 Answers2

1

I haven't seen the result of your print_r but I'm going to guess that it's an array of WP_Post objects. prepare_item_for_response requires a single WP_Post as the first argument and you're passing it an array.

Try this, after you set $latest_revision:

if (!is_array($latest_revision) || !count($latest_revision))
    return null;
$latest_revision = array_values($latest_revision)[0];

The array_values call is a quick and easy way to re-index your array.

This should give you a single post and not an array.

Update: per your own answer to your question and the questions you asked.

prepare_item_for_response rewrites a WP_Post object as something that can be serialized uniformly by the REST controller to output. For instance, it handles attachments rather than simply ignoring them. If you simply returned a WP_Post object over, say, JSON, you'd miss out on a lot of the post content. You can think of prepare_response_for_collection as the array version of the same thing. Really, it's more like the WP_Query version of the same thing so the WP rest controller can act as one-stop shopping to return lists of WP_Post objects to a REST consumer.

rest_ensure_response does something similar for any REST response. It hides the loose typing of PHP (and WP), where actions like function calls can return nothing or indeterminate things, from REST, where every request must have an appropriate response. It's not much more than a wrapper that is aware of WP_Error.

Stephan Samuel
  • 648
  • 6
  • 12
  • This did indeed help, I now don't get any notices anymore. But still the REST API response seems to be different from the one I get from my custom route: `{ "ID": 478, "post_author": "1", "post_date": "2017-11-20 17:22:11", "post_date_gmt": "2017-11-20 16:22:11", "post_content": "Content", "post_title": "My Post Title", "post_excerpt": "", "post_status": "inherit", "comment_status": "closed", ... etc. }` So I guess the `prepare_item_for_response` did not help... – Merc Nov 20 '17 at 22:42
1

I think I found a solution: Using $postController = new \WP_REST_Revisions_Controller('revision'); would not return a content field, so I had to use $postController = new \WP_REST_Posts_Controller('post'); although my result would actually be a 'revision'.

Furthermore I used prepare_item_for_response, prepare_response_for_collection and rest_ensure_response. Unfortunately I don't really know what these methods are actually doing...?

My new code:

/**
 * Add a new API route for a post or pages preview
 */

class Preview_To_REST_API_Controller extends WP_REST_Controller {

    //The namespace and version for the REST SERVER
    var $namespace = 'previews/v';
    var $version   = '1';

    public function register_routes() {
        $namespace = $this->namespace . $this->version;
        $base      = 'preview';
        register_rest_route( $namespace, '/' . $base, array(
            array(
                    'methods'         => WP_REST_Server::READABLE,
                    'callback'        => array( $this, 'get_preview' ),
                    'permission_callback'   => array( $this, 'get_permission' )
                )
        )  );
    }

    // Register our REST Server
    public function hook_rest_server(){
        add_action( 'rest_api_init', array( $this, 'register_routes' ) );
    }

    public function get_permission(){
        if ( ! current_user_can( 'edit_posts' ) ) {
            return new WP_Error( 'rest_forbidden', esc_html__( 'You do not have permissions to view this data.', 'my-text-domain' ), array( 'status' => 401 ) );
        }

        // This approach blocks the endpoint operation. You could alternatively do this by an un-blocking approach, by returning false here and changing the permissions check.
        return true;
    }

    public function get_preview( WP_REST_Request $request ){
        // use the helper methods to get the parameters
        $id = $request->get_param( 'id' );

        // Only return the newest
        $args = array (
            'order' => 'DESC',
            'orderby' => 'date',
            'posts_per_page' => '1'
        );

        // Preview version is saved in the latest revision of the page/post
        $latest_revision = wp_get_post_revisions( $id, $args );

        if (!is_array($latest_revision) || !count($latest_revision)){
            return null;
        }
        $latest_revision = array_values($latest_revision)[0];

        $postController = new \WP_REST_Posts_Controller('post');
        $response = $postController->prepare_item_for_response( $latest_revision, $request );
        $data = $postController->prepare_response_for_collection( $response );


        if ($latest_revision) {
            // Use the current method to get the only element in the revisions array
            // [0] does not return anything, because the Array's value is saved
            // as the ID key: Array[$id], but this ID we need to find out first
            $revision_id = $latest_revision -> ID;
            $acf_fields = get_fields($revision_id);

            if ( empty($latest_revision) ) {
                return null;
            }

            // Add acf fields to latest revision
            $data['acf'] = $acf_fields;

            return rest_ensure_response($data);

        } else {
            return null;
        }
    }
}

$preview_controller = new Preview_To_REST_API_Controller();
$preview_controller->hook_rest_server();

This gives me neat results like:

{
    "id": 478,
    "date": "2017-11-20T23:51:10",
    "date_gmt": "2017-11-20T22:51:10",
    "guid": {
        "rendered": "http://localhost:3000/51-autosave-v1/"
    },
    "modified": "2017-11-20T23:51:10",
    "modified_gmt": "2017-11-20T22:51:10",
    "slug": "51-autosave-v1",
    "status": "inherit",
    "type": "revision",
    "link": "http://localhost:3000/51-autosave-v1/",
    "title": {
        "rendered": "my title"
    },
        "content": {
        "rendered": "",
        "protected": false
    },
    "excerpt": {
        "rendered": "",
        "protected": false
    },
    "author": 1,
    "featured_media": 0,
    "comment_status": "closed",
    "ping_status": "closed",
    "sticky": false,
    "template": "", ... etc.

If some feels like explaining what I did, I would be glad to read about it.

Cheers

Merc
  • 4,241
  • 8
  • 52
  • 81
  • 1
    Updated my original answer with a little description of what that stuff is doing that you're using and you don't know why it works. :-) – Stephan Samuel Nov 21 '17 at 00:31
  • Thanks man. Appreciate it! Also marked your answer as the correct one. I think it deserves it :) – Merc Nov 23 '17 at 14:02
  • Much appreciated and glad to help. You seem to know what you're doing and be willing to experiment. All of the WP code is in the codex online and also installed wherever you're developing. Don't be afraid to open a file and see what it does. That's often the best source of answers! – Stephan Samuel Nov 24 '17 at 15:45
  • @Merc can you help here https://stackoverflow.com/questions/67667044/how-to-get-post-revisions-in-my-custom-rest-api-wordpress – Ricky May 24 '21 at 06:45