On the initial call the store-locator-plus.php MUP is called first an is loading with the initMySLP _jsonp call from the embed script.
the SLPlus::initialize_after_plugins_loaded() is being called.
DOING_AJAX is not set, so createobject_AJAX() is not called.
MySLP_REST_API->get_options() is called, which expects SLP->AJAX to be set, but it is not.
The front-end/locations.js is using jQuery.ajax() but it is calling a REST API url, not an AJAX listener endpoint… from the locations.js call…
Update the MySLP_REST_API->get_options() method to use SLP_Ajax::get_instance() to ensure that object is instantiated before use when not doing an AJAX call.
Use the main MySLP::getUserBlogId() to fetch blog IDs. Use SLP_Ajax::get_instance() versus the uninitialized slplus->AjaxHandler to get the SLP AJAX instance.
slplus->AJAX (slplus->AJAX_Handler) was not initialized because the call is a REST call not an AJAX call.
Store Locator Plus® was updated with a “pub/sub” model a few years ago. This feature is a managed interface for jQuery Callbacks that allows disparate Javascript functions to interact with each other. It works much like hooks and filters in WordPress, but solely within the JavaScript runtime space.
The basic premise is any JavaScript function can call a “publish” method that fires off a stack of callbacks.
slp_Filter('slp_map_ready').publish(cslmap);
What is run by those callbacks is set by having a JavaScript module call a “subscribe” method.
Location bulk actions allow for users to work on multiple locations at once. Export a list of locations, delete select locations from the location table, geocode locations ,and more.
2209.12
Export locations is our first attempt at making bulk actions work more like a single page application.
Instead of doing a form post and submitting the location list, then reading in some PHP-driven variables to set the JavaScript behavior — in 2209.12 the export process bypasses the form submit.
Bypass is handled by using a pub/sub module with a dynamic filter name “exportBulkAction”. This tells the main SLP JavaScript process to execute the callback stack instead of pushing a form submit. This allows the AJAX call to happen immediately without a full page re-render.
2208.15
The apply button processing is handled in the main plugin’s admin.js file.
It uses jQuery to process form variables and prepare the #locationForm to be submitted.
When submitted it sends the request back to WordPress for standard “full page load” processing.
The process starts with a standard form submit from the Bulk Actions “Apply” button.
WordPress sets up a JavaScript variable location_import that has info about the action “export”.
This triggers a jQuery(document).ready() call that fires another request via AJAX back to WordPress with some new parameters in place.
That AJAX call eventually sends back a CSV file that is stored in a DOM div that was prepared with the jQuery() document loader when the refreshed page (post original submit) was rendered.
An excess number of option value lookups was discovered while testing Store Locator Plus® 5.13.8 where the option/label_directions was searched multiple times for a single map render. To reduce load on servers this should only happen once, at the start of the initial map rendering.
This is not a blatant “it did something wrong” or “didn’t do something it should” type of bug. This is a performance and resource usage bug and may require some new architcture.
The manifestation:
Debugging path… investigation
Looking for label_directions in the code…
The initial search through the plugins reveals places the settings are created, set, stored, and managed but not the “JavaScript hook” being fired via /wp-json (the REST API for WordPress). So we need to look deeper.
Looking for options/ in the code…
Let’s try the start of the REST API path after the generic /wp-json/store-locator-plus/v2/ part…
We can ignore the assets/ directory… but what else is in here…
Ahhh… the slp_core.js call to slplus.rest_url + ‘options/’ + attribute looks sus…a
Solution (planned for SLP 5.14)
There was a problem in slp_core.js with value testing.
Turns out an empty value causes multiple REST calls. The following is an invalid test to see if a property exists, which is what we want here. Previously it was checking the value was SET and had a “non-falsy” value. In JavaScript lots of things are “kind of true” or “kind of false” (aka Falsy), for example the empty string “” is FALSE. That is not what we want here.
In slp_core.js we ant to replace the if (!<var>) with if (! var.hasOwnProperty()) …
Cause
The call to /v2/options/label_directions was firing once for every location because the default value for the directions label is empty (“”). This was evaluating to false, which forced the SLP JavaScript to query the server to get the value, which set it to “”.
For each location that was rendered the setting was checked… “is the label for directions set?” or more accurately “Is the label for directions set and NOT empty?”. Every time it came back saying “no, it is NOT set” or rather “It IS empty” … so the code would then go ask the REST API server for the label value.
The front end sends a request back to WordPress via AJAX to request a list of locations. Part of that request encodes all of the form field entries, including the category drop down selection, into a query-encoded (key/value pairs with an & delimiter) string that is send in the formData property to the backend.
The category ID is not being parsed and filtered properly.
Looking At The Data Request
Via browser developer tools and the network I/O inspector, XHR filter…
HTTP Method: POST URL: /wp-admin/admin-ajax.php Request Payload…
You can see the formdata property in the POST body along with the encoded form data including the cat=5 entry. This is where the requests tells the back end to limit results to those that have WordPress category ID #5 attached to the location.
Diving Into The Code
On the back end the AJAX is routed through the Store Locator Plus® base plugin via the standard WordPress hooks and filters. These are setup in the SLP_AJAX class via this add_ajax_hooks method:
Where the csl_ajax_search eventually finds its way to the find_locations method.
Debugging the find_locations method it looks like the formdata variable coming in via the superglobal $_REQUEST variable is not being set properly.
Digging Into The Malformed Variables
Looking at the call stack the form data is mishandled causing the & in the query string to be encoded as &, this causes wp_parse_args() to set the property name incorrect as you can see in the debugging screenshot below.
Turns out the wp_kses_post() is too aggressive with the data munging and converts & to & which then throws wp_parse_args() for a loop.
Instead we need to use esc_url_raw() to leave most URL entities intact.
This looks like a problem with the AJAX processor on the backend. Need to look into ajax_onload.
csl_ajax_onload refs
The problem was with the Power add on trying to validate SLP location IDs when updating map markers. The intent is to NOT manipulate a map marker with an invalid ID as it can cause more problems downstream.
Resolution
DO NOT check_admin_referer() for the AJAX calls. The easiest way to check that , since we do not have an easily-accessible reference variable for “hey, I’m running an AJAX query now” is to simply ensure the nonce we want to validate even exists in the first place.
SLP 5.13.5 prerelease + Power 5.11 has a problem found during testing… the admin add locations breaks (probably a lot of other things as well).
The browser console:
This is related to moving to wp_add_inline_script() vs. the wp_localize_script() based on findings that indicate wp_localize_script() may a not work with block themes and it is really intended for i18n/l10n.
Power needs it. handle: slppower_manage_locations data stack: {"ajax_nonce":"50164ddb77"}
SLP (base plugin) needs it. handle: slp_manage_locations data stack: {"ajax_nonce":"50164ddb77"}
However, a code search shows that ONLY the base plugin admin JavaScript is using/referencing wp_data…
Why is Power setting up an enqueue of this file?
Code Analysis of enqueue_admin_javascript()…
This is always true:
if ( ! empty( $tab_js_settings ) ) {
Because this is always set further up in the code…
// All our JS will now have an AJAX_NONCE setting. $tab_js_settings = array( 'ajax_nonce' => wp_create_nonce('slp_ajax'), );
They key player here “ajax_nonce” set above is ONLY ever used here… SLP base plugin admin.js… which is only called when doing a change_option. That is fired from both the base plugin and Power add on, HOWEVER… they both reference the SLP_ADMIN.change_option() method in JavaScript.
ajax_nonce usage in JavaScript is only in SLP base plugin admin.js (SLP_ADMIN) change_option() method attached to the options property (SLP_ADMIN.options.change_option). All calls to the SLP_ADMIN.options.change_option() call
Resolution
Do not set the baseline tab_js_settings in SLP_BaseClass_Admin.php
This will stop the default (only) ajax_nonce PHP array entry from being set, effectively short-circuiting the enqueue of the script from the Power add-on.
While this fixes the issue short-term and stops overloading the tab_js_settings, if Power (or any other add on) finds it necessary to add some custom entries to the tab_js_settings array, it will break again. This is a fragile patch.
The change…
Add the handle to the JavaScript variable definition
The current setup is to hard-code the JS environment variable to “wp_data” for ALL add-ons. This will make them “fight” per the message above.
Instead of blindly referencing a generic hard-coded “wp_data” variable, let’s change that to be based on the add-on name.
WordPress does some odd things when it comes to managing the DOM elements, especially when it comes to JavaScript and CSS. WordPress has crafted their own creative PHP methodology for getting data from PHP into JavaScript when loading the scripts. This allows for JavaScript variables to be “pre-initialized” when the scripts are first rendered.
Useful for getting a known starting state in JavaScript based on PHP state and logic that has run up to the point that the scripts are loaded.
Not standard AT ALL given today’s single page app and other advanced methodologies just as REST queries, etc. that could do the same job.
Originally all of the SLP settings forms had to be submitted by clicking a save button. This runs a typical form post + full page render… old-school web 1.X style.
Along the way work was done to utilize JavaScript triggers and save data with an onBlur on settings form fields. The tech uses jQuery using an AJAX-type model to send a single form field to the WordPress AJAX processor to manage saving a single field without re-rendering the entire page or sending the entire form.
How It Is Triggered
SLP base plugin’s admin.js initializes and hooks a change_option JS function to any INPUT type field with a quick_save CSS class attached.
var qs_inputs = jQuery('.quick_save').find(':input');
qs_inputs.on('change', function (e) {
change_option(e.currentTarget);
});
qs_inputs.on('blur', function (e) {
var start_val = e.currentTarget.defaultValue;
if (e.currentTarget.value !== start_val) {
change_option(e.currentTarget);
}
});