I am working in a WordPress site that has many custom post types, and extensive use of Advanced Custom Fields (ACF). The site also uses node modules, custom code from scratch, and Git in favor of a pre-built theme and "typical" plugins. Many of the tasks I do involve working with code already written.
The site is a safari business. We have Continents, Countries (sometimes called Destinations in certain contexts), Places, and Testimonials - all are custom posts. None of which use any taxonomies at all, ever. The testimonials already have custom fields via ACF connecting the Testimonials with appropriate Continents, Countries, and Places.
My issue is pretty complex, and I'll do my best to explain it.
My goal
Create an Ajax filter to display Testimonials by Continent, Country, and (eventually) Place, without a plugin. Continent and Country can be select
fields, and only one is required, optionally both can be set.
The site already has an almost identical version of this, working. It has dropdown select
s that filter one of the other custom post types, Itineraries. What I need is exactly like this, just for Testimonials instead of Itineraries. That page is /.
It uses NPM Select-custom and a few PHP and JS files. I've included two of them below.
What I have tried
I have made duplicated files of the Itineraries files for Testimonials, and trying to make those work just like that Itineraries page working example is. It might be easier to write meta_queries
and if so I'll need a little guidance there, but i thought this would be easier. These are the files, including sections I've commented out because they were related to one of the dropdowns from the itineraries page that don't need to be on this testimonials page, like trip budget, or because it's part of me figuring out how this all works.
Where I am stuck I've pushed the code to the staging site, and you can see the console errors. Can you help me troubleshoot the api 404 call? The already existing Itineraries page linked above has the same, but without error. You can click the Continent or Country selects and you'll get an alert, which must be related to the api call. Screenshot here showing what you'll see in console.
If there's anything else to include or add I am happy to.
hero-filter-testimonials.js
import '../elements/filter-results-grid';
$(document).ready(function () {
const $body = $('body');
const $filterTestimonials = $('.filter-testimonials');
$body.on('click', '.btn-load-more', function (e) {
$(e.currentTarget).hide();
getResults(null, parseInt(e.currentTarget.dataset.page));
});
$('.clear-all').click(function () {
window.history.replaceState(null, null, '#');
triggerSearch(getParamsObject(), $filterTestimonials);
});
continentContainer.find('.custom-select__option').on('click', function (e) {
let continentId = e.currentTarget.dataset.value;
showCountriesOptions(continentId);
});
function showCountriesOptions(continentId = null) {
const $selectContainer = $filterTestimonials.find('#destination').parent();
console.log('Line 38 hero-filter-testimonials.js: continentId: ' + continentId);
if (continentId) {
$selectContainer.find('.custom-select__option').hide();
$selectContainer.find(".custom-select__option[data-continent='" + continentId + "']").show();
} else {
$selectContainer.find('.custom-select__option').not('[data-value="placeholder"]').show();
}
}
function getResults(origin = null, page = 1) {
console.log("getResults(): " + getResults);
let data = {action: 'get_testimonial_results'};
$filterTestimonials.find('.filter-panel').hide();
const $select = $filterTestimonials.find('.custom-select');
data = getDataFromSelectors(data, $select);
if (origin && origin === 'continent') {
if (data['destination']) {
delete data['destination'];
resetSingleSelector($filterTestimonials.find('#destination')[0]);
}
}
$filterTestimonials.find('#special_departures').prop("checked");
console.log("Line below is log.data");
console.log(data);
console.log("Line below is dir.data");
console.dir(data);
updateUrl(data);
if (Object.keys(data).length === 1) {
getContainer().show();
getResultsContainer().hide();
$body.find('.clear-all-btn-container').hide();
} else {
let $loader = $body.find('.filter-loader');
$body.find('.clear-all-btn-container').show();
getFilterResults('get_testimonial_results', 'Testimonials', data, $loader, page);
}
addBadges($filterTestimonials, ['continent']);
}
window.getResults = getResults;
let data = getParamsObject();
console.log("Line below is data");
console.table(data);
console.log("Line below is $filterTestimonials");
console.table($filterTestimonials);
// console.log(JSON.stringify($filterTestimonials,null,2));
triggerSearch(data, $filterTestimonials);
});
testimonials_results.php
This page makes a custom api call that returns a 404, and I don't know why. It also has the query to get testimonials posts.
<?php
add_action('rest_api_init', function () {
register_rest_route('ejourneysnineteen/v1', '/get_testimonial_results', array(
'methods' => 'POST',
'callback' => 'get_testimonial_results',
'permission_callback' => function () {
return true;
}
));
});
function get_testimonial_results() {
$pageSize = $_REQUEST['pageSize'] ?? 10;
$args = [
'posts_per_page' => $pageSize,
'ignore_sticky_posts' => 1,
'post_type' => 'testimonial',
'orderby' => 'title',
'order' => 'ASC',
];
if (isset($_REQUEST['page'])) {
$args['paged'] = $_REQUEST['page'];
}
if (isset($_REQUEST['continent']) ||
isset($_REQUEST['destination'])
// isset($_REQUEST['region']) ||
// isset($_REQUEST['accommodation_type']
) {
// $metaQueries = ['relation' => 'AND'];
if (isset($_REQUEST['region'])) {
$regions = explode(',', $_REQUEST['region']);
$subquery = [
'relation' => 'OR'
];
foreach ($regions as $region) {
$subquery[] = [
'key' => 'accommodation_region',
'value' => $region,
'compare' => 'LIKE'
];
}
$metaQueries[] = $subquery;
}
else {
if (isset($_REQUEST['destination'])) {
$metaQueries[] = [
'key' => 'testimonial_country',
'value' => $_REQUEST['destination'],
'compare' => 'LIKE',
];
}
else {
if (isset($_REQUEST['continent'])) {
$metaQueries[] = [
'key' => 'testimonial_continent',
'value' => $_REQUEST['continent'],
'compare' => 'LIKE',
];
}
}
}
$args = array_merge($args, [
'meta_query' => $metaQueries
]);
}
$result = [];
if (count($metaQueries) > 1) {
$query = new WP_Query($args);
foreach ($query->get_posts() as $post) {
$post_thumbnail_id = get_post_thumbnail_id($post);
$image_url = fly_get_attachment_image_src($post_thumbnail_id, [500, 500], true);
$result[] = [
'link' => get_the_permalink($post),
'title' => $post->post_title,
'excerpt' => html_entity_decode(ellipsis(get_the_excerpt($post), 80)),
'image_url' => $image_url,
'category' => 'Testimonial'
];
}
}
wp_send_json_success([
'results' => $result,
'total' => $query->found_posts,
'pages' => $query->max_num_pages,
]);
}
There are other files but I'm so stuck I'm not sure if I should add them all there or not. Is this enough to help me troubleshoot the api call 404?
I am working in a WordPress site that has many custom post types, and extensive use of Advanced Custom Fields (ACF). The site also uses node modules, custom code from scratch, and Git in favor of a pre-built theme and "typical" plugins. Many of the tasks I do involve working with code already written.
The site is a safari business. We have Continents, Countries (sometimes called Destinations in certain contexts), Places, and Testimonials - all are custom posts. None of which use any taxonomies at all, ever. The testimonials already have custom fields via ACF connecting the Testimonials with appropriate Continents, Countries, and Places.
My issue is pretty complex, and I'll do my best to explain it.
My goal
Create an Ajax filter to display Testimonials by Continent, Country, and (eventually) Place, without a plugin. Continent and Country can be select
fields, and only one is required, optionally both can be set.
The site already has an almost identical version of this, working. It has dropdown select
s that filter one of the other custom post types, Itineraries. What I need is exactly like this, just for Testimonials instead of Itineraries. That page is https://extraordinaryjourneys.com/plan-your-trip/itineraries/.
It uses NPM Select-custom and a few PHP and JS files. I've included two of them below.
What I have tried
I have made duplicated files of the Itineraries files for Testimonials, and trying to make those work just like that Itineraries page working example is. It might be easier to write meta_queries
and if so I'll need a little guidance there, but i thought this would be easier. These are the files, including sections I've commented out because they were related to one of the dropdowns from the itineraries page that don't need to be on this testimonials page, like trip budget, or because it's part of me figuring out how this all works.
Where I am stuck I've pushed the code to the staging site, and you can see the console errors. Can you help me troubleshoot the api 404 call? The already existing Itineraries page linked above has the same, but without error. https://staging.extraordinaryjourneys.com/testimonial-troubleshooting You can click the Continent or Country selects and you'll get an alert, which must be related to the api call. Screenshot here showing what you'll see in console.
If there's anything else to include or add I am happy to.
hero-filter-testimonials.js
import '../elements/filter-results-grid';
$(document).ready(function () {
const $body = $('body');
const $filterTestimonials = $('.filter-testimonials');
$body.on('click', '.btn-load-more', function (e) {
$(e.currentTarget).hide();
getResults(null, parseInt(e.currentTarget.dataset.page));
});
$('.clear-all').click(function () {
window.history.replaceState(null, null, '#');
triggerSearch(getParamsObject(), $filterTestimonials);
});
continentContainer.find('.custom-select__option').on('click', function (e) {
let continentId = e.currentTarget.dataset.value;
showCountriesOptions(continentId);
});
function showCountriesOptions(continentId = null) {
const $selectContainer = $filterTestimonials.find('#destination').parent();
console.log('Line 38 hero-filter-testimonials.js: continentId: ' + continentId);
if (continentId) {
$selectContainer.find('.custom-select__option').hide();
$selectContainer.find(".custom-select__option[data-continent='" + continentId + "']").show();
} else {
$selectContainer.find('.custom-select__option').not('[data-value="placeholder"]').show();
}
}
function getResults(origin = null, page = 1) {
console.log("getResults(): " + getResults);
let data = {action: 'get_testimonial_results'};
$filterTestimonials.find('.filter-panel').hide();
const $select = $filterTestimonials.find('.custom-select');
data = getDataFromSelectors(data, $select);
if (origin && origin === 'continent') {
if (data['destination']) {
delete data['destination'];
resetSingleSelector($filterTestimonials.find('#destination')[0]);
}
}
$filterTestimonials.find('#special_departures').prop("checked");
console.log("Line below is log.data");
console.log(data);
console.log("Line below is dir.data");
console.dir(data);
updateUrl(data);
if (Object.keys(data).length === 1) {
getContainer().show();
getResultsContainer().hide();
$body.find('.clear-all-btn-container').hide();
} else {
let $loader = $body.find('.filter-loader');
$body.find('.clear-all-btn-container').show();
getFilterResults('get_testimonial_results', 'Testimonials', data, $loader, page);
}
addBadges($filterTestimonials, ['continent']);
}
window.getResults = getResults;
let data = getParamsObject();
console.log("Line below is data");
console.table(data);
console.log("Line below is $filterTestimonials");
console.table($filterTestimonials);
// console.log(JSON.stringify($filterTestimonials,null,2));
triggerSearch(data, $filterTestimonials);
});
testimonials_results.php
This page makes a custom api call that returns a 404, and I don't know why. It also has the query to get testimonials posts.
<?php
add_action('rest_api_init', function () {
register_rest_route('ejourneysnineteen/v1', '/get_testimonial_results', array(
'methods' => 'POST',
'callback' => 'get_testimonial_results',
'permission_callback' => function () {
return true;
}
));
});
function get_testimonial_results() {
$pageSize = $_REQUEST['pageSize'] ?? 10;
$args = [
'posts_per_page' => $pageSize,
'ignore_sticky_posts' => 1,
'post_type' => 'testimonial',
'orderby' => 'title',
'order' => 'ASC',
];
if (isset($_REQUEST['page'])) {
$args['paged'] = $_REQUEST['page'];
}
if (isset($_REQUEST['continent']) ||
isset($_REQUEST['destination'])
// isset($_REQUEST['region']) ||
// isset($_REQUEST['accommodation_type']
) {
// $metaQueries = ['relation' => 'AND'];
if (isset($_REQUEST['region'])) {
$regions = explode(',', $_REQUEST['region']);
$subquery = [
'relation' => 'OR'
];
foreach ($regions as $region) {
$subquery[] = [
'key' => 'accommodation_region',
'value' => $region,
'compare' => 'LIKE'
];
}
$metaQueries[] = $subquery;
}
else {
if (isset($_REQUEST['destination'])) {
$metaQueries[] = [
'key' => 'testimonial_country',
'value' => $_REQUEST['destination'],
'compare' => 'LIKE',
];
}
else {
if (isset($_REQUEST['continent'])) {
$metaQueries[] = [
'key' => 'testimonial_continent',
'value' => $_REQUEST['continent'],
'compare' => 'LIKE',
];
}
}
}
$args = array_merge($args, [
'meta_query' => $metaQueries
]);
}
$result = [];
if (count($metaQueries) > 1) {
$query = new WP_Query($args);
foreach ($query->get_posts() as $post) {
$post_thumbnail_id = get_post_thumbnail_id($post);
$image_url = fly_get_attachment_image_src($post_thumbnail_id, [500, 500], true);
$result[] = [
'link' => get_the_permalink($post),
'title' => $post->post_title,
'excerpt' => html_entity_decode(ellipsis(get_the_excerpt($post), 80)),
'image_url' => $image_url,
'category' => 'Testimonial'
];
}
}
wp_send_json_success([
'results' => $result,
'total' => $query->found_posts,
'pages' => $query->max_num_pages,
]);
}
There are other files but I'm so stuck I'm not sure if I should add them all there or not. Is this enough to help me troubleshoot the api call 404?
Share Improve this question asked Feb 10, 2022 at 20:42 Ben BlueBen Blue 1171 silver badge8 bronze badges 5 |1 Answer
Reset to default 1As I said in my comment, I made a POST request to your custom endpoint (here) and then received a 404 error with this message: "No route was found matching the URL and request method.", which likely means the route was never registered, or that the request method was not supported, i.e. not in the methods
list specified during registration (using register_rest_route()
).
So you just need to ensure the route is registered properly and that your (AJAX) request is using the correct HTTP request method, and then the 404 error would be gone.
And in your case, you can ensure the route is registered properly by loading the testimonials_results.php
file from within the theme's functions.php
file. E.g.
// Assuming the file is at the same level as the functions.php file
require_once __DIR__ . '/testimonials_results.php';
Other issues in your code
The
$metaQueries
variable is not defined, and should be defined before theif (isset($_REQUEST['continent']) ...)
line.The
isset($_REQUEST['region'])
should not be commented out, because otherwise if only theregion
parameter specified, then your custom posts query would never run.The above posts query will also run only if
$metaQueries
has 2 or more items, so if the array is empty or has just 1 item, the$query
in yourreturn
part would be an undefined and results in a PHP notice sayingUndefined variable: query
.And it's up to you on how should that be fixed.. but for example, you could add an
else
, and return an error.When the
WP_Query
class is instantiated with a non-empty args, i.e.new WP_Query($args)
as opposed tonew WP_Query()
, theget_posts()
method will be automatically called, hence you should not call it manually.So replace the
$query->get_posts()
with$query->posts
to access/retrieve the posts that already retrieved via the automatic call to theget_posts()
method.From the REST API handbook:
Importantly, a REST API route’s callback should always return data; it shouldn’t attempt to send the response body itself. This ensures that the additional processing that the REST API server does, like handling linking/embedding, sending headers, etc… takes place. In other words, don’t call
die( wp_json_encode( $data )
); orwp_send_json( $data )
.So don't call
wp_send_json_success()
(which useswp_send_json()
), and simply return the data. I.e.return [ 'results' => $result, ... ];
Additional Notes/Info
WordPress has a
__return_true()
function, so you could simply do'permission_callback' => '__return_true'
:)The
$args = array_merge($args, [ 'meta_query' => $metaQueries ]);
could just be written as$args['meta_query'] = $metaQueries;
..
$metaQueries
should be defined before theif (isset($_REQUEST['continent']) ...)
line), but as for the 404 error, the full message I saw (here via a POST request) is, "No route was found matching the URL and request method.", so how are you loading thetestimonials_results.php
file? Is itinclude
-d orrequire
-d from within thefunctions.php
file? Does your AJAX request actually make a POST request? – Sally CJ Commented Feb 11, 2022 at 13:11rest_testimonial_query
hook to modify the query args so that you could just use the default endpoint to fetch the testimonial posts.. – Sally CJ Commented Feb 11, 2022 at 13:23testimonials_results
at all. I added it as arequire_once
infunctions.php
. That got rid of the 404! Thank you for taking a look. What's the best practice for this, here - edit the question for the other issues, upvote comment, or something else? – Ben Blue Commented Feb 11, 2022 at 14:07