380 lines
13 KiB
PHP
380 lines
13 KiB
PHP
<?php
|
|
|
|
class FacetWP_Request
|
|
{
|
|
|
|
/* (array) FacetWP-related GET variables */
|
|
public $url_vars = [];
|
|
|
|
/* (mixed) The main query vars */
|
|
public $query_vars = null;
|
|
|
|
/* (boolean) FWP template shortcode? */
|
|
public $is_shortcode = false;
|
|
|
|
/* (boolean) Is a FacetWP refresh? */
|
|
public $is_refresh = false;
|
|
|
|
/* (boolean) Initial load? */
|
|
public $is_preload = false;
|
|
|
|
|
|
function __construct() {
|
|
$this->process_json();
|
|
$this->intercept_request();
|
|
}
|
|
|
|
|
|
/**
|
|
* application/json requires processing the raw PHP input stream
|
|
*/
|
|
function process_json() {
|
|
$json = file_get_contents( 'php://input' );
|
|
if ( 0 === strpos( $json, '{' ) ) {
|
|
$post_data = json_decode( $json, true );
|
|
$action = $post_data['action'] ?? '';
|
|
|
|
if ( is_string( $action ) && 0 === strpos( $action, 'facetwp' ) ) {
|
|
$_POST = $post_data;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* If AJAX and the template is "wp", return the buffered HTML
|
|
* Otherwise, store the GET variables for later use
|
|
*/
|
|
function intercept_request() {
|
|
$action = isset( $_POST['action'] ) ? sanitize_key( $_POST['action'] ) : '';
|
|
|
|
$valid_actions = [
|
|
'facetwp_refresh',
|
|
'facetwp_autocomplete_load'
|
|
];
|
|
|
|
$this->is_refresh = ( 'facetwp_refresh' == $action );
|
|
$this->is_preload = ! in_array( $action, $valid_actions );
|
|
$prefix = FWP()->helper->get_setting( 'prefix' );
|
|
$is_css_tpl = isset( $_POST['data']['template'] ) && 'wp' == $_POST['data']['template'];
|
|
|
|
// Disable the admin bar to prevent JSON issues
|
|
if ( $this->is_refresh ) {
|
|
add_filter( 'show_admin_bar', '__return_false' );
|
|
}
|
|
|
|
// Pageload
|
|
if ( $this->is_preload ) {
|
|
$features = [ 'paged', 'per_page', 'sort' ];
|
|
$valid_names = wp_list_pluck( FWP()->helper->get_facets(), 'name' );
|
|
$valid_names = array_merge( $valid_names, $features );
|
|
|
|
// Store GET variables
|
|
foreach ( $valid_names as $name ) {
|
|
if ( isset( $_GET[ $prefix . $name ] ) && '' !== $_GET[ $prefix . $name ] ) {
|
|
$new_val = stripslashes_deep( $_GET[ $prefix . $name ] );
|
|
$new_val = in_array( $name, $features ) ? $new_val : explode( ',', $new_val );
|
|
$this->url_vars[ $name ] = $new_val;
|
|
}
|
|
}
|
|
|
|
$this->url_vars = apply_filters( 'facetwp_preload_url_vars', $this->url_vars );
|
|
}
|
|
// Populate $_GET
|
|
else {
|
|
$data = stripslashes_deep( $_POST['data'] );
|
|
|
|
if ( ! empty( $data['http_params']['get'] ) ) {
|
|
foreach ( $data['http_params']['get'] as $key => $val ) {
|
|
if ( ! isset( $_GET[ $key ] ) ) {
|
|
$_GET[ $key ] = $val;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( $this->is_preload || $is_css_tpl ) {
|
|
add_filter( 'posts_pre_query', [ $this, 'maybe_abort_query' ], 10, 2 );
|
|
add_action( 'pre_get_posts', [ $this, 'sacrificial_lamb' ], 998 );
|
|
add_action( 'pre_get_posts', [ $this, 'update_query_vars' ], 999 );
|
|
}
|
|
|
|
if ( ! $this->is_preload && $is_css_tpl && 'facetwp_autocomplete_load' != $action ) {
|
|
add_action( 'shutdown', [ $this, 'inject_template' ], 0 );
|
|
ob_start();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* FacetWP runs the archive query before WP gets the chance.
|
|
* This hook prevents the query from running twice, by letting us inject the
|
|
* first query's posts (and counts) into the "main" query.
|
|
*/
|
|
function maybe_abort_query( $posts, $query ) {
|
|
$do_abort = apply_filters( 'facetwp_archive_abort_query', true, $query );
|
|
$has_query_run = ( ! empty( FWP()->facet->query ) );
|
|
|
|
if ( $do_abort && $has_query_run && isset( $this->query_vars ) ) {
|
|
|
|
// New var; any changes to $query will cause is_main_query() to return false
|
|
$query_vars = $query->query_vars;
|
|
|
|
// If paged = 0, set to 1 or the compare will fail
|
|
if ( empty( $query_vars['paged'] ) ) {
|
|
$query_vars['paged'] = 1;
|
|
}
|
|
|
|
// Only intercept the identical query
|
|
if ( $query_vars === $this->query_vars ) {
|
|
$posts = FWP()->facet->query->posts;
|
|
$query->found_posts = FWP()->facet->query->found_posts;
|
|
$query->max_num_pages = FWP()->facet->query->max_num_pages;
|
|
}
|
|
}
|
|
|
|
return $posts;
|
|
}
|
|
|
|
|
|
/**
|
|
* Fixes https://core.trac.wordpress.org/ticket/40393
|
|
*/
|
|
function sacrificial_lamb( $query ) {
|
|
}
|
|
|
|
|
|
/**
|
|
* Force FacetWP to use the default WP query
|
|
*/
|
|
function update_query_vars( $query ) {
|
|
|
|
if ( isset( $this->query_vars ) // Ran already
|
|
|| $this->is_shortcode // Skip shortcode template
|
|
|| ( is_admin() && ! wp_doing_ajax() ) // Skip admin
|
|
|| ( wp_doing_ajax() && ! $this->is_refresh ) // Skip other ajax
|
|
|| ! $this->is_main_query( $query ) // Not the main query
|
|
) {
|
|
return;
|
|
}
|
|
|
|
// Set the flag
|
|
$query->set( 'facetwp', true );
|
|
|
|
// If "s" is an empty string and no post_type is set, WP sets
|
|
// post_type = "any". We want to prevent this except on the search page.
|
|
if ( '' == $query->get( 's' ) && ! isset( $_GET['s'] ) ) {
|
|
$query->set( 's', null );
|
|
}
|
|
|
|
// Set the initial query vars, needed for render()
|
|
$this->query_vars = $query->query_vars;
|
|
|
|
// Notify
|
|
do_action( 'facetwp_found_main_query' );
|
|
|
|
// Generate the FWP output
|
|
$data = ( $this->is_preload ) ? $this->process_preload_data() : $this->process_post_data();
|
|
$this->output = FWP()->facet->render( $data );
|
|
|
|
// Set the updated query vars, needed for maybe_abort_query()
|
|
$this->query_vars = FWP()->facet->query->query_vars;
|
|
|
|
// Set the updated query vars
|
|
$force_query = apply_filters( 'facetwp_preload_force_query', false, $query );
|
|
|
|
if ( ! $this->is_preload || ! empty( $this->url_vars ) || $force_query ) {
|
|
$query->query_vars = FWP()->facet->query_args;
|
|
}
|
|
|
|
if ( 'product_query' == $query->get( 'wc_query' ) ) {
|
|
wc_set_loop_prop( 'total', FWP()->facet->pager_args['total_rows'] );
|
|
wc_set_loop_prop( 'per_page', FWP()->facet->pager_args['per_page'] );
|
|
wc_set_loop_prop( 'total_pages', FWP()->facet->pager_args['total_pages'] );
|
|
wc_set_loop_prop( 'current_page', FWP()->facet->pager_args['page'] );
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Is this the main query?
|
|
*/
|
|
function is_main_query( $query ) {
|
|
if ( 'yes' == FWP()->helper->get_setting( 'strict_query_detection', 'no' ) ) {
|
|
$is_main_query = ( $query->is_main_query() );
|
|
}
|
|
else {
|
|
$is_main_query = ( $query->is_main_query() || $query->is_archive );
|
|
}
|
|
|
|
$is_main_query = ( $query->is_singular || $query->is_feed ) ? false : $is_main_query;
|
|
$is_main_query = ( $query->get( 'suppress_filters', false ) ) ? false : $is_main_query; // skip get_posts()
|
|
$is_main_query = ( '' !== $query->get( 'facetwp' ) ) ? (bool) $query->get( 'facetwp' ) : $is_main_query; // flag
|
|
return apply_filters( 'facetwp_is_main_query', $is_main_query, $query );
|
|
}
|
|
|
|
|
|
/**
|
|
* Process the AJAX $_POST data
|
|
* This gets passed into FWP()->facet->render()
|
|
*/
|
|
function process_post_data() {
|
|
$data = stripslashes_deep( $_POST['data'] );
|
|
$facets = $data['facets'];
|
|
$extras = $data['extras'] ?? [];
|
|
$frozen_facets = $data['frozen_facets'] ?? [];
|
|
|
|
$params = [
|
|
'facets' => [],
|
|
'template' => $data['template'],
|
|
'frozen_facets' => $frozen_facets,
|
|
'http_params' => $data['http_params'],
|
|
'extras' => $extras,
|
|
'soft_refresh' => (int) $data['soft_refresh'],
|
|
'is_bfcache' => (int) $data['is_bfcache'],
|
|
'first_load' => (int) $data['first_load'], // skip the template?
|
|
'paged' => (int) $data['paged'],
|
|
];
|
|
|
|
foreach ( $facets as $facet_name => $selected_values ) {
|
|
$params['facets'][] = [
|
|
'facet_name' => $facet_name,
|
|
'selected_values' => $selected_values,
|
|
];
|
|
}
|
|
|
|
return $params;
|
|
}
|
|
|
|
|
|
/**
|
|
* On initial pageload, preload the data
|
|
*
|
|
* This gets called twice; once in the template shortcode (to grab only the template)
|
|
* and again in FWP()->display->front_scripts() to grab everything else.
|
|
*
|
|
* Two calls are needed for timing purposes; the template shortcode often renders
|
|
* before some or all of the other FacetWP-related shortcodes.
|
|
*/
|
|
function process_preload_data( $template_name = false, $overrides = [] ) {
|
|
|
|
if ( false === $template_name ) {
|
|
$template_name = $this->template_name ?? 'wp';
|
|
}
|
|
|
|
$this->template_name = $template_name;
|
|
|
|
// Is this a template shortcode?
|
|
$this->is_shortcode = ( 'wp' != $template_name );
|
|
|
|
$params = [
|
|
'facets' => [],
|
|
'template' => $template_name,
|
|
'http_params' => [
|
|
'get' => $_GET,
|
|
'uri' => FWP()->helper->get_uri(),
|
|
'url_vars' => $this->url_vars,
|
|
],
|
|
'frozen_facets' => [],
|
|
'soft_refresh' => 1, // skip the facets
|
|
'is_preload' => 1,
|
|
'is_bfcache' => 0,
|
|
'first_load' => 0, // load the template
|
|
'extras' => [],
|
|
'paged' => 1,
|
|
];
|
|
|
|
// Support "/page/X/" on preload
|
|
if ( ! empty( $this->query_vars['paged'] ) ) {
|
|
$params['paged'] = (int) $this->query_vars['paged'];
|
|
}
|
|
|
|
foreach ( $this->url_vars as $key => $val ) {
|
|
if ( 'paged' == $key ) {
|
|
$params['paged'] = $val;
|
|
}
|
|
elseif ( 'per_page' == $key || 'sort' == $key ) {
|
|
$params['extras'][ $key ] = $val;
|
|
}
|
|
else {
|
|
$params['facets'][] = [
|
|
'facet_name' => $key,
|
|
'selected_values' => $val,
|
|
];
|
|
}
|
|
}
|
|
|
|
// Override the defaults
|
|
$params = array_merge( $params, $overrides );
|
|
|
|
return $params;
|
|
}
|
|
|
|
|
|
/**
|
|
* This gets called from FWP()->display->front_scripts(), when we finally
|
|
* know which shortcodes are on the page.
|
|
*
|
|
* Since we already got the template HTML on the first process_preload_data() call,
|
|
* this time we're grabbing everything but the template.
|
|
*
|
|
* The return value of this method gets passed into the 2nd argument of
|
|
* process_preload_data().
|
|
*/
|
|
function process_preload_overrides( $items ) {
|
|
$overrides = [];
|
|
$url_vars = FWP()->request->url_vars;
|
|
|
|
foreach ( $items['facets'] as $name ) {
|
|
$overrides['facets'][] = [
|
|
'facet_name' => $name,
|
|
'selected_values' => $url_vars[ $name ] ?? [],
|
|
];
|
|
}
|
|
|
|
if ( isset( $items['extras']['counts'] ) ) {
|
|
$overrides['extras']['counts'] = true;
|
|
}
|
|
if ( isset( $items['extras']['pager'] ) ) {
|
|
$overrides['extras']['pager'] = true;
|
|
}
|
|
if ( isset( $items['extras']['per_page'] ) ) {
|
|
$overrides['extras']['per_page'] = $url_vars['per_page'] ?? 'default';
|
|
}
|
|
if ( isset( $items['extras']['sort'] ) ) {
|
|
$overrides['extras']['sort'] = $url_vars['sort'] ?? 'default';
|
|
}
|
|
|
|
$overrides['soft_refresh'] = 0; // load the facets
|
|
$overrides['first_load'] = 1; // skip the template
|
|
|
|
return $overrides;
|
|
}
|
|
|
|
|
|
/**
|
|
* Inject the page HTML into the JSON response
|
|
* We'll cherry-pick the content from the HTML using front.js
|
|
*/
|
|
function inject_template() {
|
|
$html = ob_get_clean();
|
|
|
|
// Throw an error
|
|
if ( empty( $this->output['settings'] ) ) {
|
|
$html = __( 'FacetWP was unable to auto-detect the post listing', 'fwp' );
|
|
}
|
|
// Grab the <body> contents
|
|
else {
|
|
preg_match( "/<body(.*?)>(.*?)<\/body>/s", $html, $matches );
|
|
|
|
if ( ! empty( $matches ) ) {
|
|
$html = trim( $matches[2] );
|
|
}
|
|
}
|
|
|
|
$this->output['template'] = $html;
|
|
do_action( 'facetwp_inject_template', $this->output );
|
|
wp_send_json( $this->output );
|
|
}
|
|
}
|