Merged in feature/plugins-update (pull request #9)

wp plugin updates from pantheon

* wp plugin updates from pantheon
This commit is contained in:
Tony Volpe
2023-12-15 18:08:21 +00:00
parent 28c21bf9b1
commit 779393381f
577 changed files with 154305 additions and 0 deletions

View File

@@ -0,0 +1,517 @@
<?php
class FacetWP_Integration_ACF
{
public $fields = [];
public $parent_type_lookup = [];
public $repeater_row;
function __construct() {
add_filter( 'facetwp_facet_sources', [ $this, 'facet_sources' ] );
add_filter( 'facetwp_facet_orderby', [ $this, 'facet_orderby' ], 10, 2 );
add_filter( 'facetwp_indexer_query_args', [ $this, 'lookup_acf_fields' ] );
add_filter( 'facetwp_indexer_post_facet', [ $this, 'index_acf_values' ], 1, 2 );
add_filter( 'facetwp_acf_display_value', [ $this, 'index_source_other' ], 1, 2 );
add_filter( 'facetwp_builder_item_value', [ $this, 'layout_builder_values' ], 999, 2 );
}
/**
* Add ACF fields to the Data Sources dropdown
*/
function facet_sources( $sources ) {
$fields = $this->get_fields();
$choices = [];
foreach ( $fields as $field ) {
$field_id = $field['hierarchy'];
$field_name = $field['name'];
$field_label = '[' . $field['group_title'] . '] ' . $field['parents'] . $field['label'];
$choices[ "acf/$field_id" ] = $field_label;
// remove "hidden" ACF fields
unset( $sources['custom_fields']['choices'][ "cf/_$field_name" ] );
}
if ( ! empty( $choices ) ) {
$sources['acf'] = [
'label' => 'ACF',
'choices' => $choices,
'weight' => 5
];
}
return $sources;
}
/**
* If the facet "Sort by" value is "Term order", then preserve
* the custom order of certain ACF fields (checkboxes, radio, etc.)
*/
function facet_orderby( $orderby, $facet ) {
if ( isset( $facet['source'] ) && isset( $facet['orderby'] ) ) {
if ( 0 === strpos( $facet['source'], 'acf/' ) && 'term_order' == $facet['orderby'] ) {
$source_parts = explode( '/', $facet['source'] );
$field_id = array_pop( $source_parts );
$field_object = get_field_object( $field_id );
if ( ! empty( $field_object['choices'] ) ) {
$choices = $field_object['choices'];
$choices = implode( "','", esc_sql( $choices ) );
$orderby = "FIELD(f.facet_display_value, '$choices')";
}
}
}
return $orderby;
}
/**
* Index ACF field data
*/
function index_acf_values( $return, $params ) {
$defaults = $params['defaults'];
$facet = $params['facet'];
if ( isset( $facet['source'] ) && 'acf/' == substr( $facet['source'], 0, 4 ) ) {
$hierarchy = explode( '/', substr( $facet['source'], 4 ) );
// support "User Post Type" plugin
$object_id = apply_filters( 'facetwp_acf_object_id', $defaults['post_id'] );
// get values (for sub-fields, use the parent repeater)
$value = get_field( $hierarchy[0], $object_id, false );
// handle repeater values
if ( 1 < count( $hierarchy ) ) {
$parent_field_key = array_shift( $hierarchy );
$value = $this->process_field_value( $value, $hierarchy, $parent_field_key );
// get the sub-field properties
$sub_field = get_field_object( $hierarchy[0], $object_id, false, false );
foreach ( $value as $key => $val ) {
$this->repeater_row = $key;
$rows = $this->get_values_to_index( $val, $sub_field, $defaults );
$this->index_field_values( $rows );
}
}
else {
// get the field properties
$field = get_field_object( $hierarchy[0], $object_id, false, false );
// index values
$rows = $this->get_values_to_index( $value, $field, $defaults );
$this->index_field_values( $rows );
}
return true;
}
return $return;
}
/**
* Hijack the "facetwp_indexer_query_args" hook to lookup the fields once
*/
function lookup_acf_fields( $args ) {
$this->get_fields();
return $args;
}
/**
* Grab all ACF fields
*/
function get_fields() {
add_action( 'pre_get_posts', [ $this, 'disable_wpml' ] );
$field_groups = acf_get_field_groups();
remove_action( 'pre_get_posts', [ $this, 'disable_wpml' ] );
foreach ( $field_groups as $field_group ) {
$fields = acf_get_fields( $field_group );
if ( ! empty( $fields ) ) {
$this->flatten_fields( $fields, $field_group );
}
}
return $this->fields;
}
/**
* We need to get field groups in ALL languages
*/
function disable_wpml( $query ) {
$query->set( 'suppress_filters', true );
$query->set( 'lang', '' );
}
/**
* Extract field values from the repeater array
*/
function process_field_value( $value, $hierarchy, $parent_field_key ) {
$temp_val = [];
// prevent PHP8 fatal error on invalid lookup field
$parent_field_type = $this->parent_type_lookup[ $parent_field_key ] ?? 'none';
if ( ! is_array( $value ) || 'none' == $parent_field_type ) {
return $temp_val;
}
// reduce the hierarchy array
$field_key = array_shift( $hierarchy );
// group
if ( 'group' == $parent_field_type ) {
if ( 0 == count( $hierarchy ) ) {
$temp_val[] = $value[ $field_key ];
}
else {
return $this->process_field_value( $value[ $field_key ], $hierarchy, $field_key );
}
}
// repeater
else {
if ( 0 == count( $hierarchy ) ) {
foreach ( $value as $val ) {
$temp_val[] = $val[ $field_key ];
}
}
else {
foreach ( $value as $outer ) {
if ( isset( $outer[ $field_key ] ) ) {
foreach ( $outer[ $field_key ] as $inner ) {
$temp_val[] = $inner;
}
}
}
return $this->process_field_value( $temp_val, $hierarchy, $field_key );
}
}
return $temp_val;
}
/**
* Get an array of $params arrays
* Useful for indexing and grabbing values for the Layout Builder
* @since 3.4.0
*/
function get_values_to_index( $value, $field, $params ) {
$value = maybe_unserialize( $value );
$type = $field['type'];
$output = [];
// checkboxes
if ( 'checkbox' == $type || 'select' == $type || 'radio' == $type ) {
if ( false !== $value ) {
foreach ( (array) $value as $val ) {
$display_value = isset( $field['choices'][ $val ] ) ?
$field['choices'][ $val ] :
$val;
$params['facet_value'] = $val;
$params['facet_display_value'] = $display_value;
$output[] = $params;
}
}
}
// relationship
elseif ( 'relationship' == $type || 'post_object' == $type || 'page_link' == $type ) {
if ( false !== $value ) {
foreach ( (array) $value as $val ) {
// does the post exist?
if ( false !== get_post_type( $val ) ) {
$params['facet_value'] = $val;
$params['facet_display_value'] = get_the_title( $val );
$output[] = $params;
}
}
}
}
// user
elseif ( 'user' == $type ) {
if ( false !== $value ) {
foreach ( (array) $value as $val ) {
$user = get_user_by( 'id', $val );
// does the user exist?
if ( false !== $user ) {
$params['facet_value'] = $val;
$params['facet_display_value'] = $user->display_name;
$output[] = $params;
}
}
}
}
// taxonomy
elseif ( 'taxonomy' == $type ) {
if ( ! empty( $value ) ) {
foreach ( (array) $value as $val ) {
global $wpdb;
$term_id = (int) $val;
$term = $wpdb->get_row( "SELECT name, slug FROM {$wpdb->terms} WHERE term_id = '$term_id' LIMIT 1" );
// does the term exist?
if ( null !== $term ) {
$params['facet_value'] = $term->slug;
$params['facet_display_value'] = $term->name;
$params['term_id'] = $term_id;
$output[] = $params;
}
}
}
}
// date_picker
elseif ( 'date_picker' == $type ) {
$formatted = $this->format_date( $value );
$params['facet_value'] = $formatted;
$params['facet_display_value'] = apply_filters( 'facetwp_acf_display_value', $formatted, $params );
$output[] = $params;
}
// true_false
elseif ( 'true_false' == $type ) {
$display_value = ( 0 < (int) $value ) ? __( 'Yes', 'fwp-front' ) : __( 'No', 'fwp-front' );
$params['facet_value'] = $value;
$params['facet_display_value'] = $display_value;
$output[] = $params;
}
// google_map
elseif ( 'google_map' == $type ) {
if ( isset( $value['lat'] ) && isset( $value['lng'] ) ) {
$params['facet_value'] = $value['lat'];
$params['facet_display_value'] = $value['lng'];
$params['place_details'] = $value;
$output[] = $params;
}
}
// text
else {
$params['facet_value'] = $value;
$params['facet_display_value'] = apply_filters( 'facetwp_acf_display_value', $value, $params );
$output[] = $params;
}
return $output;
}
/**
* Index values
*/
function index_field_values( $rows ) {
foreach ( $rows as $params ) {
FWP()->indexer->index_row( $params );
}
}
/**
* Handle "source_other" setting
*/
function index_source_other( $value, $params ) {
if ( ! empty( $params['facet_name'] ) ) {
$facet = FWP()->helper->get_facet_by_name( $params['facet_name'] );
if ( ! empty( $facet['source_other'] ) ) {
$hierarchy = explode( '/', substr( $facet['source_other'], 4 ) );
// support "User Post Type" plugin
$object_id = apply_filters( 'facetwp_acf_object_id', $params['post_id'] );
// get the value
$value = get_field( $hierarchy[0], $object_id, false );
// handle repeater values
if ( 1 < count( $hierarchy ) ) {
$parent_field_key = array_shift( $hierarchy );
$value = $this->process_field_value( $value, $hierarchy, $parent_field_key );
$value = $value[ $this->repeater_row ];
}
}
if ( 'date_range' == $facet['type'] ) {
$value = $this->format_date( $value );
}
}
return $value;
}
/**
* Format dates in YYYY-MM-DD
*/
function format_date( $str ) {
if ( 8 == strlen( $str ) && ctype_digit( $str ) ) {
$str = substr( $str, 0, 4 ) . '-' . substr( $str, 4, 2 ) . '-' . substr( $str, 6, 2 );
}
return $str;
}
/**
* Generates a flat array of fields within a specific field group
*/
function flatten_fields( $fields, $field_group, $hierarchy = '', $parents = '' ) {
foreach ( $fields as $field ) {
// append the hierarchy string
$new_hierarchy = $hierarchy . '/' . $field['key'];
// loop again for repeater or group fields
if ( 'repeater' == $field['type'] || 'group' == $field['type'] ) {
$new_parents = $parents . $field['label'] . ' &rarr; ';
$this->parent_type_lookup[ $field['key'] ] = $field['type'];
$this->flatten_fields( $field['sub_fields'], $field_group, $new_hierarchy, $new_parents );
}
else {
$this->fields[] = [
'key' => $field['key'],
'name' => $field['name'],
'label' => $field['label'],
'hierarchy' => trim( $new_hierarchy, '/' ),
'parents' => $parents,
'group_title' => $field_group['title'],
];
}
}
}
/**
* Get the field value (support User Post Type)
* @since 3.4.1
*/
function get_field( $source, $post_id ) {
$hierarchy = explode( '/', substr( $source, 4 ) );
$object_id = apply_filters( 'facetwp_acf_object_id', $post_id );
return get_field( $hierarchy[0], $object_id );
}
/**
* Fallback values for the layout builder
* @since 3.4.0
*
* ACF return formats:
* [image, file] = array, url, id
* [select, checkbox, radio, button_group] = value, label, array (both)
* [post_object, relationship, taxonomy] = object, id
* [user] = array, object, id
* [link] = array, url
*/
function layout_builder_values( $value, $item ) {
global $post;
// exit if not an object or array
if ( is_scalar( $value ) || is_null( $value ) ) {
return $value;
}
$hierarchy = explode( '/', substr( $item['source'], 4 ) );
// support "User Post Type" plugin
$object_id = apply_filters( 'facetwp_acf_object_id', $post->ID );
// get the field properties
$field = get_field_object( $hierarchy[0], $object_id, false, false );
$type = $field['type'];
$format = $field['return_format'] ?? '';
$is_multiple = (bool) ( $field['multiple'] ?? false );
if ( ( 'post_object' == $type || 'relationship' == $type ) && 'object' == $format ) {
$output = [];
$value = is_array( $value ) ? $value : [ $value ];
foreach ( $value as $val ) {
$output[] = '<a href="' . get_permalink( $val->ID ) . '">' . esc_html( $val->post_title ) . '</a>';
}
$value = $output;
}
if ( 'taxonomy' == $type && 'object' == $format ) {
$output = [];
foreach ( $value as $val ) {
$output[] = $val->name;
}
$value = $output;
}
if ( ( 'select' == $type || 'checkbox' == $type || 'radio' == $type || 'button_group' == $type ) && 'array' == $format ) {
$value = $value['label'] ?? wp_list_pluck( $value, 'label' );
}
if ( ( 'image' == $type || 'gallery' == $type ) && 'array' == $format ) {
$value = ( 'image' == $type ) ? [ $value ] : $value;
foreach ( $value as $val ) {
$value = '<img src="' . esc_url( $val['url'] ) . '" title="' . esc_attr( $val['title'] ) . '" alt="' . esc_attr( $val['alt'] ) . '" />';
}
}
if ( 'file' == $type && 'array' == $format ) {
$value = '<a href="' . esc_url( $value['url'] ) . '">' . esc_html( $value['filename'] ) . '</a> (' . size_format( $value['filesize'], 1 ) . ')';
}
if ( 'link' == $type && 'array' == $format ) {
$value = '<a href="' . esc_url( $value['url'] ) . '" target="' . esc_attr( $value['target'] ) . '">' . esc_html( $value['title'] ) . '</a>';
}
if ( 'google_map' == $type ) {
$value = '<a href="https://www.google.com/maps/?q=' . $value['lat'] . ',' . $value['lng'] . '" target="_blank">' . esc_html( $value['address'] ) . '</a>';
}
if ( 'user' == $type && ( 'object' == $format || 'array' == $format ) ) {
$output = [];
$value = $is_multiple ? $value : [ $value ];
foreach ( $value as $val ) {
if ( 'object' == $format ) {
$output[] = $val->display_name;
}
elseif ( 'array' == $format ) {
$output[] = $val['display_name'];
}
}
$value = $output;
}
return $value;
}
}
if ( function_exists( 'acf' ) && version_compare( acf()->settings['version'], '5.0', '>=' ) ) {
FWP()->acf = new FacetWP_Integration_ACF();
}

View File

@@ -0,0 +1,6 @@
(function($) {
$().on('facetwp-loaded', function() {
$('.edd-no-js').addClass('facetwp-hidden');
$('a.edd-add-to-cart').addClass('edd-has-js');
});
})(fUtil);

View File

@@ -0,0 +1,51 @@
<?php
class FacetWP_Integration_EDD
{
function __construct() {
add_filter( 'facetwp_facet_sources', [ $this, 'exclude_data_sources' ] );
add_filter( 'edd_downloads_query', [ $this, 'edd_downloads_query' ] );
add_action( 'facetwp_assets', [ $this, 'assets' ] );
}
/**
* Trigger some EDD code on facetwp-loaded
* @since 2.0.4
*/
function assets( $assets ) {
$assets['edd.js'] = FACETWP_URL . '/includes/integrations/edd/edd.js';
return $assets;
}
/**
* Help FacetWP auto-detect the [downloads] shortcode
* @since 2.0.4
*/
function edd_downloads_query( $query ) {
$query['facetwp'] = true;
return $query;
}
/**
* Exclude specific EDD custom fields
* @since 2.4
*/
function exclude_data_sources( $sources ) {
foreach ( $sources['custom_fields']['choices'] as $key => $val ) {
if ( 0 === strpos( $val, '_edd_' ) ) {
unset( $sources['custom_fields']['choices'][ $key ] );
}
}
return $sources;
}
}
if ( is_plugin_active( 'easy-digital-downloads/easy-digital-downloads.php' ) ) {
new FacetWP_Integration_EDD();
}

View File

@@ -0,0 +1,215 @@
<?php
class FacetWP_Integration_SearchWP
{
public $keywords;
public $swp_query;
public $first_run = true;
function __construct() {
add_filter( 'facetwp_is_main_query', [ $this, 'is_main_query' ], 10, 2 );
add_action( 'pre_get_posts', [ $this, 'pre_get_posts' ], 1000, 2 );
add_filter( 'posts_pre_query', [ $this, 'posts_pre_query' ], 10, 2 );
add_filter( 'posts_results', [ $this, 'posts_results' ], 10, 2 );
add_filter( 'facetwp_facet_filter_posts', [ $this, 'search_facet' ], 10, 2 );
add_filter( 'facetwp_facet_search_engines', [ $this, 'search_engines' ] );
}
/**
* Run and cache the \SWP_Query
*/
function is_main_query( $is_main_query, $query ) {
if ( $is_main_query && $query->is_search() && ! empty( $query->get( 's' ) ) ) {
$args = stripslashes_deep( $this->get_valid_args( $query ) );
$this->keywords = $args['s'];
$this->swp_query = $this->run_query( $args );
$query->set( 'using_searchwp', true );
$query->set( 'searchwp', false );
}
return $is_main_query;
}
/**
* Whitelist supported SWP_Query arguments
*
* @link https://searchwp.com/documentation/classes/swp_query/#arguments
*/
function get_valid_args( $query ) {
$output = [];
$valid = [
's', 'engine', 'post__in', 'post__not_in', 'post_type', 'post_status',
'tax_query', 'meta_query', 'date_query', 'order', 'orderby'
];
foreach ( $valid as $arg ) {
$val = $query->get( $arg );
if ( ! empty( $val ) ) {
$output[ $arg ] = $val;
}
}
return $output;
}
/**
* Modify FacetWP's render() query to use SearchWP's results while bypassing
* WP core search. We're using this additional query to support custom query
* modifications, such as for FacetWP's sort box.
*
* The hook priority (1000) is important because this needs to run after
* FacetWP_Request->update_query_vars()
*/
function pre_get_posts( $query ) {
if ( true === $query->get( 'using_searchwp' ) ) {
if ( true === $query->get( 'facetwp' ) && ! $this->first_run ) {
$query->set( 's', '' );
$post_ids = FWP()->filtered_post_ids;
$post_ids = empty( $post_ids ) ? [ 0 ] : $post_ids;
$query->set( 'post__in', $post_ids );
if ( '' === $query->get( 'post_type' ) ) {
$query->set( 'post_type', 'any' );
$query->set( 'post_status', 'any' );
}
if ( '' === $query->get( 'orderby' ) ) {
$query->set( 'orderby', 'post__in' );
}
}
}
}
/**
* If [facetwp => false] then it's the get_filtered_post_ids() query. Return
* the raw SearchWP results to prevent the additional query.
*
* If [facetwp => true] and [first_run => true] then it's the main WP query. Return
* a non-null value to kill the query, since we don't use the results.
*
* If [facetwp => true] and [first_run => false] then it's the FacetWP render() query.
*/
function posts_pre_query( $posts, $query ) {
if ( true === $query->get( 'using_searchwp' ) ) {
if ( true === $query->get( 'facetwp' ) ) {
$query->set( 's', $this->keywords );
// kill the main WP query
if ( $this->first_run ) {
$this->first_run = false;
$page = max( $query->get( 'paged' ), 1 );
$per_page = (int) $query->get( 'posts_per_page', get_option( 'posts_per_page' ) );
$query->found_posts = count( FWP()->filtered_post_ids );
$query->max_num_pages = ( 0 < $per_page ) ? ceil( $query->found_posts / $per_page ) : 0;
return [];
}
}
else {
return $this->swp_query->posts;
}
}
return $posts;
}
/**
* Apply highlighting if available
*/
function posts_results( $posts, $query ) {
if ( true === $query->get( 'using_searchwp' ) ) {
if ( true === $query->get( 'facetwp' ) && ! $this->first_run ) {
// SearchWP 4.1+
if ( isset( $this->swp_query->query ) ) {
foreach ( $posts as $index => $post ) {
$source = \SearchWP\Utils::get_post_type_source_name( $post->post_type );
$entry = new \SearchWP\Entry( $source, $post->ID, false );
$posts[ $index ] = $entry->native( $this->swp_query->query );
}
}
}
}
return $posts;
}
/**
* For search facets, run \SWP_Query and return matching post IDs
*/
function search_facet( $return, $params ) {
$facet = $params['facet'];
$selected_values = $params['selected_values'];
$selected_values = is_array( $selected_values ) ? $selected_values[0] : $selected_values;
$engine = $facet['search_engine'] ?? '';
if ( 'search' == $facet['type'] && 0 === strpos( $engine, 'swp_' ) ) {
$return = [];
if ( empty( $selected_values ) ) {
$return = 'continue';
}
elseif ( ! empty( FWP()->unfiltered_post_ids ) ) {
$swp_query = $this->run_query([
's' => $selected_values,
'engine' => substr( $engine, 4 ),
'post__in' => FWP()->unfiltered_post_ids
]);
$return = $swp_query->posts;
}
}
return $return;
}
/**
* Run a search and return the \SWP_Query object
*/
function run_query( $args ) {
$overrides = [ 'posts_per_page' => 200, 'fields' => 'ids', 'facetwp' => true ];
$args = array_merge( $args, $overrides );
return new \SWP_Query( $args );
}
/**
* Add engines to the search facet
*/
function search_engines( $engines ) {
if ( version_compare( SEARCHWP_VERSION, '4.0', '>=' ) ) {
$settings = get_option( SEARCHWP_PREFIX . 'engines' );
foreach ( $settings as $key => $info ) {
$engines[ 'swp_' . $key ] = 'SearchWP - ' . $info['label'];
}
}
else {
$settings = get_option( SEARCHWP_PREFIX . 'settings' );
foreach ( $settings['engines'] as $key => $info ) {
$label = $info['searchwp_engine_label'] ?? __( 'Default', 'fwp' );
$engines[ 'swp_' . $key ] = 'SearchWP - ' . $label;
}
}
return $engines;
}
}
if ( defined( 'SEARCHWP_VERSION' ) ) {
new FacetWP_Integration_SearchWP();
}

View File

@@ -0,0 +1,85 @@
<?php
class FacetWP_Integration_WooCommerce_Taxonomy
{
function __construct() {
add_action( 'woocommerce_after_template_part', [ $this, 'add_loop_tag'] );
add_filter( 'get_terms', [ $this, 'adjust_term_counts' ], 10, 3 );
add_filter( 'term_link', [ $this, 'append_url_vars' ], 10, 3 );
}
/**
* Support category listings (Shop page display: Show categories)
* @since 3.3.10
*/
function add_loop_tag( $template_name ) {
if ( 'loop/loop-start.php' == $template_name ) {
echo "<!--fwp-loop-->\n";
}
}
/**
* Adjust the category listing counts when facets are selected
* @since 3.3.10
*/
function adjust_term_counts( $terms, $taxonomy, $query_vars ) {
if ( FWP()->request->is_refresh || ! empty( FWP()->request->url_vars ) ) {
if ( 'product_cat' == reset( $taxonomy ) ) {
global $wpdb, $wp_query;
$sql = $wp_query->request;
if ( false !== ( $pos = strpos( $sql, ' ORDER BY' ) ) ) {
$sql = substr( $sql, 0, $pos );
}
$post_ids = $wpdb->get_col( $sql );
if ( ! empty( $post_ids ) ) {
$term_counts = [];
$post_ids_str = implode( ',', $post_ids );
$query = "
SELECT term_id, COUNT(term_id) AS term_count
FROM {$wpdb->prefix}facetwp_index
WHERE post_id IN ($post_ids_str)
GROUP BY term_id";
$results = $wpdb->get_results( $query );
foreach ( $results as $row ) {
$term_counts[ $row->term_id ] = (int) $row->term_count;
}
foreach ( $terms as $term ) {
$term->count = $term_counts[ $term->term_id ] ?? 0;
}
}
}
}
return $terms;
}
/**
* Append facet URL variables to the category archive links
* @since 3.3.10
*/
function append_url_vars( $term_link, $term, $taxonomy ) {
if ( 'product_cat' == $taxonomy ) {
$query_string = filter_var( $_SERVER['QUERY_STRING'], FILTER_SANITIZE_URL );
if ( ! empty( $query_string ) ) {
$prefix = ( false !== strpos( $query_string, '?' ) ) ? '&' : '?';
$term_link .= $prefix . $query_string;
}
}
return $term_link;
}
}
new FacetWP_Integration_WooCommerce_Taxonomy();

View File

@@ -0,0 +1,34 @@
(function($) {
$().on('facetwp-refresh', function() {
if (! FWP.loaded) {
setup_woocommerce();
}
});
function setup_woocommerce() {
// Intercept WooCommerce pagination
$().on('click', '.woocommerce-pagination a', function(e) {
e.preventDefault();
var matches = $(this).attr('href').match(/\/page\/(\d+)/);
if (null !== matches) {
FWP.paged = parseInt(matches[1]);
FWP.soft_refresh = true;
FWP.refresh();
}
});
// Disable sort handler
$('.woocommerce-ordering').attr('onsubmit', 'event.preventDefault()');
// Intercept WooCommerce sorting
$().on('change', '.woocommerce-ordering .orderby', function(e) {
var qs = new URLSearchParams(window.location.search);
qs.set('orderby', $(this).val());
history.pushState(null, null, window.location.pathname + '?' + qs.toString());
FWP.soft_refresh = true;
FWP.refresh();
});
}
})(fUtil);

View File

@@ -0,0 +1,570 @@
<?php
class FacetWP_Integration_WooCommerce
{
public $cache = [];
public $lookup = [];
public $storage = [];
public $variations = [];
public $post_clauses = false;
function __construct() {
add_action( 'facetwp_assets', [ $this, 'assets' ] );
add_filter( 'facetwp_facet_sources', [ $this, 'facet_sources' ] );
add_filter( 'facetwp_facet_display_value', [ $this, 'translate_hardcoded_choices' ], 10, 2 );
add_filter( 'facetwp_indexer_post_facet', [ $this, 'index_woo_values' ], 10, 2 );
// Support WooCommerce product variations
$is_enabled = ( 'yes' === FWP()->helper->get_setting( 'wc_enable_variations', 'no' ) );
if ( apply_filters( 'facetwp_enable_product_variations', $is_enabled ) ) {
add_filter( 'facetwp_indexer_post_facet_defaults', [ $this, 'force_taxonomy' ], 10, 2 );
add_filter( 'facetwp_indexer_query_args', [ $this, 'index_variations' ] );
add_filter( 'facetwp_index_row', [ $this, 'attribute_variations' ], 1 );
add_filter( 'facetwp_wpdb_sql', [ $this, 'wpdb_sql' ], 10, 2 );
add_filter( 'facetwp_wpdb_get_col', [ $this, 'wpdb_get_col' ], 10, 3 );
add_filter( 'facetwp_filtered_post_ids', [ $this, 'process_variations' ] );
add_filter( 'facetwp_facet_where', [ $this, 'facet_where' ], 10, 2 );
}
// Preserve the WooCommerce sort
add_filter( 'posts_clauses', [ $this, 'preserve_sort' ], 20, 2 );
// Prevent WooCommerce from redirecting to a single result page
add_filter( 'woocommerce_redirect_single_search_result', [ $this, 'redirect_single_search_result' ] );
// Prevent WooCommerce sort (posts_clauses) when doing FacetWP sort
add_filter( 'woocommerce_default_catalog_orderby', [ $this, 'default_catalog_orderby' ] );
// Dynamic counts when Shop Page Display = "Categories" or "Both"
if ( apply_filters( 'facetwp_woocommerce_support_categories_display', false ) ) {
include( FACETWP_DIR . '/includes/integrations/woocommerce/taxonomy.php' );
}
}
/**
* Run WooCommerce handlers on facetwp-refresh
* @since 2.0.9
*/
function assets( $assets ) {
$assets['woocommerce.js'] = FACETWP_URL . '/includes/integrations/woocommerce/woocommerce.js';
return $assets;
}
/**
* Add WooCommerce-specific data sources
* @since 2.1.4
*/
function facet_sources( $sources ) {
$sources['woocommerce'] = [
'label' => __( 'WooCommerce', 'fwp' ),
'choices' => [
'woo/price' => __( 'Price' ),
'woo/sale_price' => __( 'Sale Price' ),
'woo/regular_price' => __( 'Regular Price' ),
'woo/average_rating' => __( 'Average Rating' ),
'woo/stock_status' => __( 'Stock Status' ),
'woo/on_sale' => __( 'On Sale' ),
'woo/featured' => __( 'Featured' ),
'woo/product_type' => __( 'Product Type' ),
],
'weight' => 5
];
// Move WC taxonomy choices
foreach ( $sources['taxonomies']['choices'] as $key => $label ) {
if ( 'tax/product_cat' == $key || 'tax/product_tag' == $key || 0 === strpos( $key, 'tax/pa_' ) ) {
$sources['woocommerce']['choices'][ $key ] = $label;
unset( $sources['taxonomies']['choices'][ $key ] );
}
}
return $sources;
}
/**
* Attributes for WC product variations are stored in postmeta
* @since 2.7.2
*/
function force_taxonomy( $defaults, $params ) {
if ( 0 === strpos( $defaults['facet_source'], 'tax/pa_' ) ) {
$post_id = (int) $defaults['post_id'];
if ( 'product_variation' == get_post_type( $post_id ) ) {
$defaults['facet_source'] = str_replace( 'tax/', 'cf/attribute_', $defaults['facet_source'] );
}
}
return $defaults;
}
/**
* Index product variations
* @since 2.7
*/
function index_variations( $args ) {
// Saving a single product
if ( ! empty( $args['p'] ) ) {
$post_id = (int) $args['p'];
if ( 'product' == get_post_type( $post_id ) ) {
if ( 'variable' == $this->get_product_type( $post_id ) ) {
$product = wc_get_product( $post_id );
if ( false !== $product ) {
$children = $product->get_children();
$args['post_type'] = [ 'product', 'product_variation' ];
$args['post__in'] = $children;
$args['post__in'][] = $post_id;
$args['posts_per_page'] = -1;
unset( $args['p'] );
}
}
}
}
// Force product variations to piggyback products
else {
$pt = (array) $args['post_type'];
if ( in_array( 'any', $pt ) ) {
$pt = get_post_types();
}
if ( in_array( 'product', $pt ) ) {
$pt[] = 'product_variation';
}
$args['post_type'] = $pt;
}
return $args;
}
/**
* When indexing product variations, attribute its parent product
* @since 2.7
*/
function attribute_variations( $params ) {
$post_id = (int) $params['post_id'];
// Set variation_id for all posts
$params['variation_id'] = $post_id;
if ( 'product_variation' == get_post_type( $post_id ) ) {
$params['post_id'] = wp_get_post_parent_id( $post_id );
// Lookup the term name for variation values
if ( 0 === strpos( $params['facet_source'], 'cf/attribute_pa_' ) ) {
$taxonomy = str_replace( 'cf/attribute_', '', $params['facet_source'] );
$term = get_term_by( 'slug', $params['facet_value'], $taxonomy );
if ( false !== $term ) {
$params['term_id'] = $term->term_id;
$params['facet_display_value'] = $term->name;
}
}
}
return $params;
}
/**
* Hijack filter_posts() to grab variation IDs
* @since 2.7
*/
function wpdb_sql( $sql, $facet ) {
$sql = str_replace(
'DISTINCT post_id',
'DISTINCT post_id, GROUP_CONCAT(variation_id) AS variation_ids',
$sql
);
$sql .= ' GROUP BY post_id';
return $sql;
}
/**
* Store a facet's variation IDs
* @since 2.7
*/
function wpdb_get_col( $result, $sql, $facet ) {
global $wpdb;
$facet_name = $facet['name'];
$post_ids = $wpdb->get_col( $sql, 0 ); // arrays of product IDs
$variations = $wpdb->get_col( $sql, 1 ); // variation IDs as arrays of comma-separated strings
foreach ( $post_ids as $index => $post_id ) {
$variations_array = explode( ',', $variations[ $index ] );
$type = in_array( $post_id, $variations_array ) ? 'products' : 'variations';
if ( isset( $this->cache[ $facet_name ][ $type ] ) ) {
foreach ( $variations_array as $id ) {
$this->cache[ $facet_name ][ $type ][] = $id;
}
}
else {
$this->cache[ $facet_name ][ $type ] = $variations_array;
}
}
return $result;
}
/**
* We need lookup arrays for both products and variations
* @since 2.7.1
*/
function generate_lookup_array( $post_ids ) {
global $wpdb;
$output = [];
if ( ! empty( $post_ids ) ) {
$sql = "
SELECT DISTINCT post_id, variation_id
FROM {$wpdb->prefix}facetwp_index
WHERE post_id IN (" . implode( ',', $post_ids ) . ")";
$results = $wpdb->get_results( $sql );
foreach ( $results as $result ) {
$output['get_variations'][ $result->post_id ][] = $result->variation_id;
$output['get_product'][ $result->variation_id ] = $result->post_id;
}
}
return $output;
}
/**
* Determine valid variation IDs
* @since 2.7
*/
function process_variations( $post_ids ) {
if ( empty( $this->cache ) ) {
return $post_ids;
}
$this->lookup = $this->generate_lookup_array( FWP()->unfiltered_post_ids );
// Loop through each facet's data
foreach ( $this->cache as $facet_name => $groups ) {
$this->storage[ $facet_name ] = [];
// Create an array of variation IDs
foreach ( $groups as $type => $ids ) { // products or variations
foreach ( $ids as $id ) {
$this->storage[ $facet_name ][] = $id;
// Lookup variation IDs for each product
if ( 'products' == $type ) {
if ( ! empty( $this->lookup['get_variations'][ $id ] ) ) {
foreach ( $this->lookup['get_variations'][ $id ] as $variation_id ) {
$this->storage[ $facet_name ][] = $variation_id;
}
}
}
}
}
}
$result = $this->calculate_variations();
$this->variations = $result['variations'];
$post_ids = array_intersect( $post_ids, $result['products'] );
$post_ids = empty( $post_ids ) ? [ 0 ] : $post_ids;
return $post_ids;
}
/**
* Calculate variation IDs
* @param mixed $facet_name Facet name to ignore, or FALSE
* @return array Associative array of product IDs + variation IDs
* @since 2.8
*/
function calculate_variations( $facet_name = false ) {
$new = true;
$final_products = [];
$final_variations = [];
// Intersect product + variation IDs across facets
foreach ( $this->storage as $name => $variation_ids ) {
// Skip facets in "OR" mode
if ( $facet_name === $name ) {
continue;
}
$final_variations = ( $new ) ? $variation_ids : array_intersect( $final_variations, $variation_ids );
$new = false;
}
// Lookup each variation's product ID
foreach ( $final_variations as $variation_id ) {
if ( isset( $this->lookup['get_product'][ $variation_id ] ) ) {
$final_products[ $this->lookup['get_product'][ $variation_id ] ] = true; // prevent duplicates
}
}
// Append product IDs to the variations array
$final_products = array_keys( $final_products );
foreach ( $final_products as $id ) {
$final_variations[] = $id;
}
return [
'products' => $final_products,
'variations' => array_unique( $final_variations )
];
}
/**
* Apply variation IDs to load_values() method
* @since 2.7
*/
function facet_where( $where_clause, $facet ) {
// Support facets in "OR" mode
if ( FWP()->helper->facet_is( $facet, 'operator', 'or' ) ) {
$result = $this->calculate_variations( $facet['name'] );
$variations = $result['variations'];
}
else {
$variations = $this->variations;
}
if ( ! empty( $variations ) ) {
$where_clause .= ' AND variation_id IN (' . implode( ',', $variations ) . ')';
}
return $where_clause;
}
/**
* Efficiently grab the product type without wc_get_product()
* @since 3.3.8
*/
function get_product_type( $post_id ) {
global $wpdb;
$sql = "
SELECT t.name
FROM $wpdb->terms t
INNER JOIN $wpdb->term_taxonomy tt ON tt.term_id = t.term_id AND tt.taxonomy = 'product_type'
INNER JOIN $wpdb->term_relationships tr ON tr.term_taxonomy_id = tt.term_taxonomy_id AND tr.object_id = %d";
$type = $wpdb->get_var(
$wpdb->prepare( $sql, $post_id )
);
return ( null !== $type ) ? $type : 'simple';
}
/**
* Index WooCommerce-specific values
* @since 2.1.4
*/
function index_woo_values( $return, $params ) {
$facet = $params['facet'];
$defaults = $params['defaults'];
$post_id = (int) $defaults['post_id'];
$post_type = get_post_type( $post_id );
// Index out of stock products?
$index_all = ( 'yes' === FWP()->helper->get_setting( 'wc_index_all', 'no' ) );
$index_all = apply_filters( 'facetwp_index_all_products', $index_all );
if ( 'product' == $post_type || 'product_variation' == $post_type ) {
$product = wc_get_product( $post_id );
if ( ! $product || ( ! $index_all && ! $product->is_in_stock() ) ) {
return true; // skip
}
}
// Default handling
if ( 'product' != $post_type || empty( $facet['source'] ) ) {
return $return;
}
// Ignore product attributes with "Used for variations" ticked
if ( 0 === strpos( $facet['source'], 'tax/pa_' ) ) {
if ( 'variable' == $this->get_product_type( $post_id ) ) {
$attrs = $product->get_attributes();
$attr_name = str_replace( 'tax/', '', $facet['source'] );
if ( isset( $attrs[ $attr_name ] ) && 1 === $attrs[ $attr_name ]['is_variation'] ) {
return true; // skip
}
}
}
// Custom woo fields
if ( 0 === strpos( $facet['source'], 'woo/' ) ) {
$source = substr( $facet['source'], 4 );
// Price
if ( 'price' == $source || 'sale_price' == $source || 'regular_price' == $source ) {
if ( $product->is_type( 'variable' ) ) {
$method_name = "get_variation_$source";
$price_min = $product->$method_name( 'min' ); // get_variation_price()
$price_max = $product->$method_name( 'max' );
}
else {
$method_name = "get_$source";
$price_min = $price_max = $product->$method_name(); // get_price()
}
$defaults['facet_value'] = $price_min;
$defaults['facet_display_value'] = $price_max;
FWP()->indexer->index_row( $defaults );
}
// Average Rating
elseif ( 'average_rating' == $source ) {
$rating = $product->get_average_rating();
$defaults['facet_value'] = $rating;
$defaults['facet_display_value'] = $rating;
FWP()->indexer->index_row( $defaults );
}
// Stock Status
elseif ( 'stock_status' == $source ) {
$in_stock = $product->is_in_stock();
$defaults['facet_value'] = (int) $in_stock;
$defaults['facet_display_value'] = $in_stock ? 'In Stock' : 'Out of Stock';
FWP()->indexer->index_row( $defaults );
}
// On Sale
elseif ( 'on_sale' == $source ) {
if ( $product->is_on_sale() ) {
$defaults['facet_value'] = 1;
$defaults['facet_display_value'] = 'On Sale';
FWP()->indexer->index_row( $defaults );
}
}
// Featured
elseif ( 'featured' == $source ) {
if ( $product->is_featured() ) {
$defaults['facet_value'] = 1;
$defaults['facet_display_value'] = 'Featured';
FWP()->indexer->index_row( $defaults );
}
}
// Product Type
elseif ( 'product_type' == $source ) {
$type = $product->get_type();
$defaults['facet_value'] = $type;
$defaults['facet_display_value'] = $type;
FWP()->indexer->index_row( $defaults );
}
return true; // skip
}
return $return;
}
/**
* Allow certain hard-coded choices to be translated dynamically
* instead of stored as translated in the index table
* @since 3.9.6
*/
function translate_hardcoded_choices( $label, $params ) {
$source = $params['facet']['source'];
if ( 'woo/stock_status' == $source ) {
$label = ( 'In Stock' == $label ) ? __( 'In Stock', 'fwp-front' ) : __( 'Out of Stock', 'fwp-front' );
}
elseif ( 'woo/on_sale' == $source ) {
$label = __( 'On Sale', 'fwp-front' );
}
elseif ( 'woo/featured' == $source ) {
$label = __( 'Featured', 'fwp-front' );
}
return $label;
}
/**
* WooCommerce removes its sort hooks after the main product_query runs
* We need to preserve the sort for FacetWP to work
*
* @since 3.2.8
*/
function preserve_sort( $clauses, $query ) {
if ( ! apply_filters( 'facetwp_woocommerce_preserve_sort', true ) ) {
return $clauses;
}
$prefix = FWP()->helper->get_setting( 'prefix' );
$using_sort = isset( FWP()->facet->http_params['get'][ $prefix . 'sort' ] );
if ( 'product_query' == $query->get( 'wc_query' ) && true === $query->get( 'facetwp' ) && ! $using_sort ) {
if ( false === $this->post_clauses ) {
$this->post_clauses = $clauses;
}
else {
$clauses['join'] = $this->post_clauses['join'];
$clauses['where'] = $this->post_clauses['where'];
$clauses['orderby'] = $this->post_clauses['orderby'];
// Narrow the main query results
$where_clause = FWP()->facet->where_clause;
if ( ! empty( $where_clause ) ) {
$column = $GLOBALS['wpdb']->posts;
$clauses['where'] .= str_replace( 'post_id', "$column.ID", $where_clause );
}
}
}
return $clauses;
}
/**
* Prevent WooCommerce from redirecting to single result page
* @since 3.3.7
*/
function redirect_single_search_result( $bool ) {
$using_facetwp = ( FWP()->request->is_refresh || ! empty( FWP()->request->url_vars ) );
return $using_facetwp ? false : $bool;
}
/**
* Prevent WooCommerce sort when a FacetWP sort is active
* @since 3.6.8
*/
function default_catalog_orderby( $orderby ) {
$sort = FWP()->helper->get_setting( 'prefix' ) . 'sort';
return isset( $_GET[ $sort ] ) ? 'menu_order' : $orderby;
}
}
if ( is_plugin_active( 'woocommerce/woocommerce.php' ) ) {
new FacetWP_Integration_WooCommerce();
}

View File

@@ -0,0 +1,150 @@
<?php
/**
* Builds or purges the FacetWP index.
*/
class FacetWP_Integration_WP_CLI
{
/**
* Index facet data.
*
* ## OPTIONS
*
* [--ids=<ids>]
* : Index specific post IDs (comma-separated).
*
* [--facets=<facets>]
* : Index specific facet names (comma-separated).
*/
function index( $args, $assoc_args ) {
$index_all = true;
if ( isset( $assoc_args['ids'] ) ) {
if ( empty( $assoc_args['ids'] ) ) {
WP_CLI::error( 'IDs empty.' );
}
$ids = preg_replace( '/\s+/', '', $assoc_args['ids'] );
$ids = explode( ',', $ids );
$post_ids = array_filter( $ids, 'ctype_digit' );
$index_all = false;
}
else {
$post_ids = FWP()->indexer->get_post_ids_to_index();
}
if ( isset( $assoc_args['facets'] ) ) {
if ( empty( $assoc_args['facets'] ) ) {
WP_CLI::error( 'Facets empty.' );
}
$facets = [];
$facet_names = preg_replace( '/\s+/', '', $assoc_args['facets'] );
$facet_names = explode( ',', $facet_names );
foreach ( $facet_names as $name ) {
$facet = FWP()->helper->get_facet_by_name( $name );
if ( false !== $facet ) {
$facets[] = $facet;
}
}
$index_all = false;
}
else {
$facets = FWP()->helper->get_facets();
}
$progress = WP_CLI\Utils\make_progress_bar( 'Indexing:', count( $post_ids ) );
// prep
if ( $index_all ) {
FWP()->indexer->manage_temp_table( 'create' );
}
else {
$assoc_args['pre_index'] = true;
$this->purge( $args, $assoc_args );
}
// manually load value modifiers
FWP()->indexer->load_value_modifiers( $facets );
// index
foreach ( $post_ids as $post_id ) {
FWP()->indexer->index_post( $post_id, $facets );
$progress->tick();
}
// cleanup
if ( $index_all ) {
update_option( 'facetwp_last_indexed', time(), 'no' );
FWP()->indexer->manage_temp_table( 'replace' );
FWP()->indexer->manage_temp_table( 'delete' );
}
$progress->finish();
WP_CLI::success( 'Indexing complete.' );
}
/**
* Purge facet data.
*
* ## OPTIONS
*
* [--ids=<ids>]
* : Purge specific post IDs (comma-separated).
*
* [--facets=<facets>]
* : Purge specific facet names (comma-separated).
*/
function purge( $args, $assoc_args ) {
global $wpdb;
$table = FWP()->indexer->table;
if ( ! isset( $assoc_args['ids'] ) && ! isset( $assoc_args['facets'] ) ) {
$sql = "TRUNCATE TABLE $table";
}
else {
$where = [];
if ( isset( $assoc_args['ids'] ) ) {
if ( empty( $assoc_args['ids'] ) ) {
WP_CLI::error( 'IDs empty.' );
}
$ids = preg_replace( '/\s+/', '', ',' . $assoc_args['ids'] );
$ids = explode( ',', $ids );
$post_ids = array_filter( $ids, 'ctype_digit' );
$post_ids = implode( "','", $post_ids );
$where[] = "post_id IN ('$post_ids')";
}
if ( isset( $assoc_args['facets'] ) ) {
if ( empty( $assoc_args['facets'] ) ) {
WP_CLI::error( 'Facets empty.' );
}
$facet_names = preg_replace( '/\s+/', '', $assoc_args['facets'] );
$facet_names = explode( ',', $facet_names );
$facet_names = array_map( 'esc_sql', $facet_names );
$facet_names = implode( "','", $facet_names );
$where[] = "facet_name IN ('$facet_names')";
}
$sql = "DELETE FROM $table WHERE " . implode( ' AND ', $where );
}
$wpdb->query( $sql );
if ( ! isset( $assoc_args['pre_index'] ) ) {
WP_CLI::success( 'Purge complete.' );
}
}
}
if ( defined( 'WP_CLI' ) && WP_CLI ) {
WP_CLI::add_command( 'facetwp', 'FacetWP_Integration_WP_CLI' );
}

View File

@@ -0,0 +1,24 @@
<?php
class FacetWP_Integration_WP_Rocket
{
function __construct() {
add_filter( 'rocket_exclude_defer_js', [ $this, 'get_exclusions' ] );
add_filter( 'rocket_delay_js_exclusions', [ $this, 'get_exclusions' ] );
add_filter( 'rocket_cdn_reject_files', [ $this, 'get_exclusions' ] );
}
function get_exclusions( $excluded ) {
$excluded[] = '(.*)facetwp(.*)';
$excluded[] = '(.*)maps.googleapis(.*)';
$excluded[] = '/jquery-?[0-9.]*(.min|.slim|.slim.min)?.js';
return $excluded;
}
}
if ( defined( 'WP_ROCKET_VERSION' ) ) {
new FacetWP_Integration_WP_Rocket();
}