wp-papi / papi

:rocket: WordPress Page Type API with custom fields
https://wp-papi.github.io
MIT License
263 stars 32 forks source link

REST API output does not include complex types #232

Closed ashervb closed 7 years ago

ashervb commented 7 years ago

What I expected

On the 3.2.0-beta1 branch, I would expect the API to return the serialized meta values for posts, arrays of posts and arrays created with the repeater field.

What happened instead

The meta values and repeater fields come back as "post". Example JSON:

[{
  "meta": {
      "home_testimonials": "post", // Relationship
      "home_hero_title": "Welcome", 
      "home_hero_image": {
          "width": 800,
          "height": 1000,
          "file": "2017\/05\/nfafn7r4.jpg",
          "sizes": {
              "thumbnail": {
                  "file": "nfafn7r4-150x150.jpg",
                  "width": 150,
                  "height": 150,
                  "mime-type": "image\/jpeg",
                  "url": "http:\/\/....\/wp-content\/uploads\/2017\/05\/nfafn7r4-150x150.jpg"
              },
              "medium": {
                  "file": "nfafn7r4-240x300.jpg",
                  "width": 240,
                  "height": 300,
                  "mime-type": "image\/jpeg",
                  "url": "http:\/\/....\/wp-content\/uploads\/2017\/05\/nfafn7r4-240x300.jpg"
              },
              "medium_large": {
                  "file": "nfafn7r4-768x960.jpg",
                  "width": 768,
                  "height": 960,
                  "mime-type": "image\/jpeg",
                  "url": "http:\/\/....\/wp-content\/uploads\/2017\/05\/nfafn7r4-768x960.jpg"
              }
          },
          "image_meta": {
              "aperture": "0",
              "credit": "",
              "camera": "",
              "caption": "",
              "created_timestamp": "0",
              "copyright": "",
              "focal_length": "0",
              "iso": "0",
              "shutter_speed": "0",
              "title": "",
              "orientation": "0",
              "keywords": []
          },
          "alt": "",
          "caption": "",
          "description": "",
          "id": 70,
          "is_image": true,
          "title": "nfafn7r4",
          "url": "http:\/\/....\/wp-content\/uploads\/2017\/05\/nfafn7r4.jpg"
      },
      "home_videos": "post", // Relationship
      "home_infographics": "post", // Repeater Field
      "home_tips": "post" // Relationship
  },
}]

If I create a hook for rest_api_init I can get the values to be output in the response:

add_action( 'rest_api_init', 'create_api_posts_meta_field' );
function create_api_posts_meta_field() {
  register_rest_field( 'page', 'meta', array(
    'get_callback' => function ( $data ) {
      $meta = get_post_meta($data['id'], '', true);
      $ret = [];
      foreach ($meta as $key => $value) {
        if (substr($k,0,1) != "_") {
          $ret[$key] = papi_get_field($data['id'], $key);
        }
      }

      return $ret;
    },
  ));
}

However, this will not include the meta value of included posts.

Steps to reproduce

Create a custom page type that uses relationships or repeater fields

What versions of softwares are you using?

frozzare commented 7 years ago

Why would you have the serialized value instead of json value in the response? It would be a lot harder to parse the data for any other language than PHP.

It may be a bug with some fields where they don't return the right value in the rest api but it shouldn't be serialized, it should be json. I can look into this when I have the time.

ashervb commented 7 years ago

Sorry, that was a mistake, by "serialized", I meant deserialized :)

I'm just expecting to see the entire associated post object / array (ideally with any associated meta), or another complex data structure (e.g. repeater) in the JSON response rather than the string "post".

I'll also try to look into it, knowing that A) that isn't the intended response and B) there shouldn't be anything else one needs to do to get that response helps narrow the search space.

frozzare commented 7 years ago

The output of all custom fields that are created with papi in the rest api response should be what papi_get_field returns but something seems wrong.

This is where you should start to debugging this https://github.com/wp-papi/papi/blob/master/src/rest-api/class-papi-rest-api-post.php#L88-L101 and each properties has rest_prepare_value method that can modify the property value before it returned to the rest api.

ashervb commented 7 years ago

Haven't traced this all the way through, but commenting out this line https://github.com/wp-papi/papi/blob/master/src/rest-api/class-papi-rest-api-post.php#L90 yields the expected response (though it does not include associated post meta data). Seems that set_property_meta_value is only called here and the property meta value gets set to a default of [] or "post" instead of the deserialized value.

frozzare commented 7 years ago

Hm, will try to take a look tomorrow, don't have any good answer right know but set_property_meta_value should set the loaded value to the property since the value already exists before prepare_property_value is called.

frozzare commented 7 years ago

Did look into this and sad to say that it the was not complete, so complex types did not work. This is now fixed and it will return the same value as papi_get_field does for all types.

ashervb commented 7 years ago

Thanks for the fix! One thing that I noticed is that relationship properties still don't include the post meta values. Is that intentional?

On my local copy to get around this, I added a filter on line 87, in the format_value function of src/properties/class-papi-property-relationship.php, apply_filters( 'papi/property/relationship/load', $post ); and then added them in manually:

  add_filter('papi/property/relationship/load', function($post) {
    $id = $post->ID;
    $meta = [];

    foreach (papi_get_slugs($id) as $name => $slugs) {
      foreach ($slugs as $slug) {
        $field = papi_get_field($id, $slug);
        if ($field) {
          $meta[$slug] = $field;
        }
      }
    }

    $post->meta = $meta;
  })
frozzare commented 7 years ago

That behaviour don't exists in Papi, properties that return a post just return a instance of WP_Post, so you have to fetch the post id and create a new request to get the meta fields for that post.

Maybe that is something we should consider adding, but right now it don't exists.