pantheon-systems / solr-power

A WordPress plugin to connect to Pantheon's Apache Solr search infrastructure, or your own!
https://wordpress.org/plugins/solr-power/
GNU General Public License v2.0
126 stars 61 forks source link

Custom WP Rest Api Endpoints #389

Closed faction23 closed 5 years ago

faction23 commented 5 years ago

Thanks for this plugin! I am running into some strangeness when activating this plugin on Pantheon with my custom WP Rest endpoints. Its possible I am missing something.

Details

I have registered a custom endpoint that accepts a few params (post type array, search, taxes, per page, paged, status) that executes WP_Query. It just returns ids and I create a tailored response just containing a couple of meta fields, title, id and type. This all works great, eg

wp-json/namespace/v1/explore?type[]=park&type[]=trail&per_page=100&status[]=pending&status[]=publish

returns posts that match those params with a tailored response as described above.

Turning on this plugin, this endpoint now ignores those args, and returns published posts only (that i get, it only indexes published) but it returns pages etc. The response is also not the shape I have described but rather a fully loaded object response that includes all Solr meta as well as all fields that would be included in a rest api response.

Is it not possible to have custom endpoints with tailored returns mix with this plugin? Thanks!

danielbachhuber commented 5 years ago

Hi @faction23,

Thanks for the report.

The response is also not the shape I have described but rather a fully loaded object response that includes all Solr meta as well as all fields that would be included in a rest api response.

Can you share the code you're using to filter the original WP_Query return value? It's possible that Solr Power is overriding the filter you're using so your filter no longer applies.

faction23 commented 5 years ago

Hey @danielbachhuber ! Thanks for taking the time to checkin on this! OK, here is the code that registers my custom endpoint and supplies my response.

class Rest {
    const NAMESPACE        = 'mynamespace';
    const VERSION          = 'v1';
    const ENDPOINT_EXPLORE = 'explore';

    public function hook() {
        add_action( 'rest_api_init', [ $this, 'register_explore_route' ], 10, 2 );
    }

    public function handle_explore_request( \WP_REST_Request $request ) {
        $post_data = [];
        $args      = [
            'fields'         => 'ids',
            'orderby'        => $request[ 'order' ],
            'paged'          => $request[ 'page' ],
            'post_status'    => $request[ 'status' ],
            'post_type'      => $request[ 'type' ],
            'posts_per_page' => $request[ 'per_page' ],
            's'              => $request[ 'search' ],
        ];
        $post_query = new \WP_Query( $args );
        $ids = $post_query->posts;

        if ( ! empty( $ids ) ) {
            foreach ( $ids as $id ) {
                $location = Map::get_marker_data( $id );
                $hero     = get_field( MetaFields::HEADER_HERO_IMAGES, $id );

                $post_data[] = [
                    'id'      => $id,
                    'type'    => get_post_type( $id ),
                    'title'   => get_the_title( $id ),
                    'lat'     => $location[ 'lat' ] ?? 0,
                    'lng'     => $location[ 'lng' ] ?? 0,
                    'excerpt' => Connected::get_hero_excerpt( $id ),
                    'image'   => ! empty( $hero[ 0 ] ) ? $hero[ 0 ][ 'sizes' ][ Image_Sizes::CARD_4X3 ] : '',
                    'activities' => Map::get_activities( $id ),
                ];
            }
        }

        return rest_ensure_response( $post_data );
    }

    public function register_explore_route() {
        register_rest_route( self::NAMESPACE . '/' . self::VERSION, '/' . self::ENDPOINT_EXPLORE . '/', [
            'methods'  => 'GET',
            'callback' => [ $this, 'handle_explore_request' ],
            'args'     => [
                'type' => [
                    'required'          => true,
                    'validate_callback' => function ( $param ) {
                        return is_array( $param );
                    },
                ],
                'per_page' => [
                    'default'           => get_option( 'posts_per_page' ),
                    'validate_callback' => function ( $param ) {
                        return is_numeric( $param );
                    },
                ],
                'page' => [
                    'default'           => 1,
                    'validate_callback' => function ( $param ) {
                        return is_numeric( $param );
                    },
                ],
                'status' => [
                    'default'           => ['publish'],
                    'validate_callback' => function ( $param ) {
                        return is_array( $param );
                    },
                ],
                'order' => [
                    'default'           => 'title',
                    'validate_callback' => function ( $param ) {
                        return is_string( $param );
                    },
                ],
                'search' => [
                    'default'           => '',
                    'validate_callback' => function ( $param ) {
                        return is_string( $param );
                    },
                ],
            ],
        ] );
    }
}

I'm doing this as i get better performance (both in query and payload weight) out of this tailored response (plus multi cpt support on one request) than by using the v2 built ins.

Anyway, with this plugin off, i get what i expect, json in the shape of my return function above, and my query args are respected. Turning this plugin on, my request args are ignored, and my response is a fully loaded object return ignoring my custom response. Essentially, it appears to totally ignore both sides of my php, but does still handle the endpoint.

Built in v2 routes work as expected with the solr plugin on. Maybe i need to tweak prio? Let me know if you would like response data samples. Thanks!

danielbachhuber commented 5 years ago

Turning this plugin on, my request args are ignored, and my response is a fully loaded object return ignoring my custom response. Essentially, it appears to totally ignore both sides of my php, but does still handle the endpoint.

That's really odd. I don't see any glaring issues in the code you've provided. Solr Power doesn't modify API responses directly, so the behavior would be in WP_Query.

Is there any chance you have some other code unexpectedly at play? I'll think on this some to see if I can come up with other ideas.

faction23 commented 5 years ago

OK, i tested and you are right! Even on the front end i was getting the same effect. I also tried out disabling all but my core plugins:

acf pro, my own core functionality. Same thing

Then i decided to test not using a wp_query where only id's where returned, and voila! The return is the normal shape i specified. Looks like part of the bug here lies in setting WP_Query to only return id's. That causes a huge pile of all of solrs internal query work to get added to result. You can view what it adds here: https://gist.github.com/faction23/022f740d7a379f259e1d9fd41ba47b7e

Note, my fields that i set ARE actually at the very end there.

If i do my query like yo:

$post_data = [];
            $args = [
                'orderby'        => 'title',
                'paged'          => 1,
                'post_status'    => 'publish',
                'post_type'      => 'campground',
                'posts_per_page' => 1,
                's'              => '',
            ];
            $post_query = new \WP_Query( $args );

            if( $post_query->have_posts() ) {
                while ( $post_query->have_posts() ) {
                    $post_query->the_post();
                    $id = get_the_ID();
                    $location = Map::get_marker_data( $id );
                    $hero     = get_field( MetaFields::HEADER_HERO_IMAGES, $id );

                    $post_data[] = [
                        'id'      => $id,
                        'type'    => get_post_type( $id ),
                        'title'   => get_the_title( $id ),
                        'lat'     => $location[ 'lat' ] ?? 0,
                        'lng'     => $location[ 'lng' ] ?? 0,
                        'excerpt' => Connected::get_hero_excerpt( $id ),
                        'image'   => ! empty( $hero[ 0 ] ) ? $hero[ 0 ][ 'sizes' ][ Image_Sizes::CARD_4X3 ] : '',
                        'activities' => Map::get_activities( $id ),
                    ];
                }
            }

Then only the fields i specify return. BUT, heading into bug number 2, even though i specified post_type, that was ignored. It return a trail even though i am specifying campground here in the updated test. Here is what a var dump gave for that above query https://gist.github.com/faction23/ae179fbd2fce5120385cfb172435b87b

Disabling solr and the query behaves normally, cpt is respected.

I tested some more, and what i found is that as soon the empty s => '' is added, thats what causes post type to be ignored. Is that by design, or should post_type arg be honored when doing a search?

In regards to versions, i am on the latest WP, acf and solr.

Thank you @danielbachhuber !

danielbachhuber commented 5 years ago

Then i decided to test not using a wp_query where only id's where returned, and voila! The return is the normal shape i specified. Looks like part of the bug here lies in setting WP_Query to only return id's.

I'm not quite sure what you're referring to here. Can you point me to the specific code?

I tested some more, and what i found is that as soon the empty s => '' is added, thats what causes post type to be ignored. Is that by design, or should post_type arg be honored when doing a search?

Oh, I think you've run into a small idiosyncrasy:

  1. Solr Power is enabled for $query->is_search(): https://github.com/pantheon-systems/solr-power/blob/d3bb9c9c53241f0037cfbc0ecc93b38c1e479a83/includes/class-solrpower-wp-query.php#L141
  2. However, if the s param itself is empty, then the Solr query isn't ever built: https://github.com/pantheon-systems/solr-power/blob/d3bb9c9c53241f0037cfbc0ecc93b38c1e479a83/includes/class-solrpower-wp-query.php#L467

In your use of WP_Query, can you try including 'solr_integrate' => true to see if that resolves your issue?

faction23 commented 5 years ago

OK! Not applying an empty search string and using 'solr_integrate' => true on the query now sees the post type and tax args being respected, thank you so much!

For the first item the issue i am seeing remains. If i add the fields arg here so that wp query only returns ids:

$post_data = [];
        $vars      = $this->get_query_vars( $request );
        $args      = [
            'fields'         => 'ids',
            'solr_integrate' => true,
            'order'          => 'ASC',
            'orderby'        => $vars[ 'order' ],
            'paged'          => $vars[ 'page' ],
            'post_status'    => $vars[ 'status' ],
            'post_type'      => $vars[ 'type' ],
            'posts_per_page' => $vars[ 'per_page' ],
            'tax_query'      => $this->get_tax_query_for_explore( $vars ),
        ];

        if( ! empty( $vars[ 'search' ] ) ) {
            $args[ 's' ] = $vars[ 'search' ];
        }

        $post_query = new \WP_Query( $args );
        $ids        = $post_query->posts;

        if ( ! empty( $ids ) ) {
            foreach ( $ids as $id ) {
                $location = Map::get_marker_data( $id );
                $hero     = get_field( MetaFields::HEADER_HERO_IMAGES, $id );

                $post_data[] = [
                    'id'         => $id,
                    'type'       => get_post_type( $id ),
                    'title'      => get_the_title( $id ),
                    'lat'        => $location[ 'lat' ] ?? 0,
                    'lng'        => $location[ 'lng' ] ?? 0,
                    'excerpt'    => Connected::get_hero_excerpt( $id ),
                    'image'      => ! empty( $hero[ 0 ] ) ? $hero[ 0 ][ 'sizes' ][ Image_Sizes::CARD_4X3 ] : '',
                    'activities' => Map::get_activities( $id ),
                ];
            }
        }

        return rest_ensure_response( $post_data );

then that post data has all of this additional cruft added to it: https://gist.github.com/faction23/022f740d7a379f259e1d9fd41ba47b7e

if i dont limit the return to just ids from wp_query then the response is not altered.

danielbachhuber commented 5 years ago

For the first item the issue i am seeing remains. If i add the fields arg here so that wp query only returns ids:

This seems like it might be a bug. Want to see if you can track it down and submit a pull request for it?

danielbachhuber commented 5 years ago

For the first item the issue i am seeing remains. If i add the fields arg here so that wp query only returns ids:

This seems like it might be a bug. Want to see if you can track it down and submit a pull request for it?

Aha, I'm not crazy. I worked on this a year and a half ago #300

danielbachhuber commented 5 years ago

Closing in favor of #336