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,180 @@
<?php
class FacetWP_API_Fetch
{
function __construct() {
add_action( 'rest_api_init', [ $this, 'register' ] );
}
// PHP < 5.3
function register() {
register_rest_route( 'facetwp/v1', '/fetch', [
'methods' => 'POST',
'callback' => [ $this, 'callback' ],
'permission_callback' => [ $this, 'permission_callback' ]
] );
}
// PHP < 5.3
function callback( $request ) {
$data = $request->get_param( 'data' );
if ( ! $request->is_json_content_type()) {
$data = empty( $data ) ? [] : json_decode( $data, true );
}
return $this->process_request( $data );
}
// PHP < 5.3
function permission_callback( $request ) {
return apply_filters( 'facetwp_api_can_access', false, $request );
}
function process_request( $params = [] ) {
global $wpdb;
$defaults = [
'facets' => [
// 'category' => [ 'acf' ]
],
'query_args' => [
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => 10,
'paged' => 1,
],
'settings' => [
'first_load' => true
]
];
$params = array_merge( $defaults, $params );
$facet_types = FWP()->helper->facet_types;
$valid_facets = [];
$facets = [];
// Validate input
$page = (int) ( $params['query_args']['paged'] ?? 1 );
$per_page = (int) ( $params['query_args']['posts_per_page'] ?? 10 );
$page = max( $page, 1 );
$per_page = ( 0 === $per_page ) ? 10 : $per_page;
$per_page = ( -1 > $per_page ) ? absint( $per_page ) : $per_page;
$params['query_args']['paged'] = $page;
$params['query_args']['posts_per_page'] = $per_page;
// Generate FWP()->facet->facets
// Required by FWP()->helper->facet_setting_exists()
foreach ( $params['facets'] as $facet_name => $facet_value ) {
$facet = FWP()->helper->get_facet_by_name( $facet_name );
if ( false !== $facet ) {
$facet['selected_values'] = (array) $facet_value;
$valid_facets[ $facet_name ] = $facet;
FWP()->facet->facets[ $facet_name ] = $facet;
}
}
// Get bucket of post IDs
$query_args = $params['query_args'];
FWP()->facet->query_args = $query_args;
$post_ids = FWP()->facet->get_filtered_post_ids( $query_args );
// SQL WHERE used by facets
$where_clause = ' AND post_id IN (' . implode( ',', $post_ids ) . ')';
// Check if empty
if ( 0 === $post_ids[0] && 1 === count( $post_ids ) ) {
$post_ids = [];
}
// get_where_clause() needs "found_posts" (keep this BELOW the empty check)
FWP()->facet->query = (object) [ 'found_posts' => count( $post_ids ) ];
// Get valid facets and their values
foreach ( $valid_facets as $facet_name => $facet ) {
$args = [
'facet' => $facet,
'where_clause' => $where_clause,
'selected_values' => $facet['selected_values'],
];
$facet_data = [
'name' => $facet['name'],
'label' => $facet['label'],
'type' => $facet['type'],
'selected' => $facet['selected_values'],
];
// Load facet choices if available
if ( method_exists( $facet_types[ $facet['type'] ], 'load_values' ) ) {
$choices = $facet_types[ $facet['type'] ]->load_values( $args );
foreach ( $choices as $key => $choice ) {
$row = [
'value' => $choice['facet_value'],
'label' => $choice['facet_display_value'],
'depth' => (int) $choice['depth'],
'count' => (int) $choice['counter'],
];
if ( isset( $choice['term_id'] ) ) {
$row['term_id'] = (int) $choice['term_id'];
}
if ( isset( $choice['parent_id'] ) ) {
$row['parent_id'] = (int) $choice['parent_id'];
}
$choices[ $key ] = $row;
}
$facet_data['choices'] = $choices;
}
// Load facet settings if available
if ( method_exists( $facet_types[ $facet['type'] ], 'settings_js' ) ) {
$facet_data['settings'] = $facet_types[ $facet['type'] ]->settings_js( $args );
}
$facets[ $facet_name ] = $facet_data;
}
$total_rows = count( $post_ids );
// Paginate?
if ( 0 < $per_page ) {
$total_pages = ceil( $total_rows / $per_page );
if ( $page > $total_pages ) {
$post_ids = [];
}
else {
$offset = ( $per_page * ( $page - 1 ) );
$post_ids = array_slice( $post_ids, $offset, $per_page );
}
}
else {
$total_pages = ( 0 < $total_rows ) ? 1 : 0;
}
// Generate the output
$output = [
'results' => $post_ids,
'facets' => $facets,
'pager' => [
'page' => $page,
'per_page' => $per_page,
'total_rows' => $total_rows,
'total_pages' => $total_pages,
]
];
return apply_filters( 'facetwp_api_output', $output );
}
}

View File

@@ -0,0 +1,27 @@
<?php
add_action( 'rest_api_init', function() {
register_rest_route( 'facetwp/v1', '/refresh', [
'methods' => 'POST',
'callback' => 'facetwp_api_refresh',
'permission_callback' => '__return_true'
] );
});
function facetwp_api_refresh( $request ) {
$params = $request->get_params();
$action = $params['action'] ?? '';
$valid_actions = [
'facetwp_refresh',
'facetwp_autocomplete_load'
];
$valid_actions = apply_filters( 'facetwp_api_valid_actions', $valid_actions );
if ( in_array( $action, $valid_actions ) ) {
do_action( $action );
}
return [];
}

View File

@@ -0,0 +1,321 @@
<?php
class FacetWP_Ajax
{
function __construct() {
add_action( 'init', [ $this, 'switchboard' ], 1000 );
}
function switchboard() {
$valid_actions = [
'resume_index',
'save_settings',
'rebuild_index',
'get_info',
'get_query_args',
'heartbeat',
'license',
'backup'
];
$action = isset( $_POST['action'] ) ? sanitize_key( $_POST['action'] ) : '';
$is_valid = false;
if ( 0 === strpos( $action, 'facetwp_' ) ) {
$action = substr( $action, 8 );
$is_valid = in_array( $action, $valid_actions );
}
if ( $is_valid ) {
// Non-authenticated
if ( in_array( $action, [ 'resume_index' ] ) ) {
$this->$action();
}
// Authenticated
elseif ( current_user_can( 'manage_options' ) ) {
if ( wp_verify_nonce( $_POST['nonce'], 'fwp_admin_nonce' ) ) {
$this->$action();
}
}
}
// Listen for API refresh call
add_action( 'facetwp_refresh', [ $this, 'refresh' ] );
// Backwards compatibility
$this->url_vars = FWP()->request->url_vars;
$this->is_preload = FWP()->request->is_preload;
}
/**
* Save admin settings
*/
function save_settings() {
$settings = $_POST['data'];
if ( isset( $settings['settings'] ) ) {
update_option( 'facetwp_settings', json_encode( $settings ), 'no' );
if ( FWP()->diff->is_reindex_needed() ) {
$response = [
'code' => 'error',
'message' => __( 'Settings saved, please re-index', 'fwp' )
];
}
else {
$response = [
'code' => 'success',
'message' => __( 'Settings saved', 'fwp' )
];
}
}
else {
$response = [
'code' => 'error',
'message' => __( 'Error: invalid JSON', 'fwp' )
];
}
wp_send_json( $response );
}
/**
* Rebuild the index table
*/
function rebuild_index() {
update_option( 'facetwp_indexing_cancelled', 'no', 'no' );
FWP()->indexer->index();
exit;
}
function get_info() {
$type = $_POST['type'];
if ( 'post_types' == $type ) {
$post_types = get_post_types( [ 'exclude_from_search' => false, '_builtin' => false ] );
$post_types = [ 'post', 'page' ] + $post_types;
sort( $post_types );
$response = [
'code' => 'success',
'message' => implode( ', ', $post_types )
];
}
elseif ( 'indexer_stats' == $type ) {
$last_indexed = get_option( 'facetwp_last_indexed' );
$last_indexed = $last_indexed ? human_time_diff( $last_indexed ) . ' ago' : 'never';
$response = [
'code' => 'success',
'message' => "last indexed: $last_indexed"
];
}
elseif ( 'cancel_reindex' == $type ) {
update_option( 'facetwp_indexing_cancelled', 'yes' );
$response = [
'code' => 'success',
'message' => 'Indexing cancelled'
];
}
elseif ( 'purge_index_table' == $type ) {
global $wpdb;
$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}facetwp_index" );
delete_option( 'facetwp_version' );
delete_option( 'facetwp_indexing' );
delete_option( 'facetwp_transients' );
$response = [
'code' => 'success',
'message' => __( 'Done, please re-index', 'fwp' )
];
}
wp_send_json( $response );
}
/**
* Return query arguments based on a Query Builder object
*/
function get_query_args() {
$query_obj = $_POST['query_obj'];
if ( is_array( $query_obj ) ) {
$query_args = FWP()->builder->parse_query_obj( $query_obj );
}
wp_send_json( $query_args );
}
/**
* Keep track of indexing progress
*/
function heartbeat() {
$output = [
'pct' => FWP()->indexer->get_progress()
];
if ( -1 == $output['pct'] ) {
$output['rows'] = FWP()->helper->get_row_counts();
}
wp_send_json( $output );
}
/**
* License activation
*/
function license() {
$license = sanitize_key( $_POST['license'] );
$request = wp_remote_post( 'https://api.facetwp.com', [
'body' => [
'action' => 'activate',
'slug' => 'facetwp',
'license' => $license,
'host' => FWP()->helper->get_http_host(),
]
] );
if ( ! is_wp_error( $request ) || 200 == wp_remote_retrieve_response_code( $request ) ) {
update_option( 'facetwp_license', $license );
update_option( 'facetwp_activation', $request['body'] );
update_option( 'facetwp_updater_last_checked', 0 );
echo $request['body'];
}
else {
echo json_encode( [
'status' => 'error',
'message' => __( 'Error', 'fwp' ) . ': ' . $request->get_error_message(),
] );
}
exit;
}
/**
* Import / export functionality
*/
function backup() {
$action_type = $_POST['action_type'];
$output = [];
if ( 'export' == $action_type ) {
$items = $_POST['items'];
if ( ! empty( $items ) ) {
foreach ( $items as $item ) {
if ( 'facet' == substr( $item, 0, 5 ) ) {
$item_name = substr( $item, 6 );
$output['facets'][] = FWP()->helper->get_facet_by_name( $item_name );
}
elseif ( 'template' == substr( $item, 0, 8 ) ) {
$item_name = substr( $item, 9 );
$output['templates'][] = FWP()->helper->get_template_by_name( $item_name );
}
}
}
echo json_encode( $output );
}
elseif ( 'import' == $action_type ) {
$settings = FWP()->helper->settings;
$import_code = $_POST['import_code'];
$overwrite = (int) $_POST['overwrite'];
if ( empty( $import_code ) || ! is_array( $import_code ) ) {
_e( 'Nothing to import', 'fwp' );
exit;
}
$status = [
'imported' => [],
'skipped' => [],
];
foreach ( $import_code as $object_type => $object_items ) {
foreach ( $object_items as $object_item ) {
$is_match = false;
foreach ( $settings[$object_type] as $key => $settings_item ) {
if ( $object_item['name'] == $settings_item['name'] ) {
if ( $overwrite ) {
$settings[$object_type][$key] = $object_item;
$status['imported'][] = $object_item['label'];
}
else {
$status['skipped'][] = $object_item['label'];
}
$is_match = true;
break;
}
}
if ( ! $is_match ) {
$settings[$object_type][] = $object_item;
$status['imported'][] = $object_item['label'];
}
}
}
update_option( 'facetwp_settings', json_encode( $settings ) );
if ( ! empty( $status['imported'] ) ) {
echo ' [<strong>' . __( 'Imported', 'fwp' ) . '</strong>] ' . implode( ', ', $status['imported'] );
}
if ( ! empty( $status['skipped'] ) ) {
echo ' [<strong>' . __( 'Skipped', 'fwp' ) . '</strong>] ' . implode( ', ', $status['skipped'] );
}
}
exit;
}
/**
* The AJAX facet refresh handler
*/
function refresh() {
global $wpdb;
$params = FWP()->request->process_post_data();
$output = FWP()->facet->render( $params );
$data = stripslashes_deep( $_POST['data'] );
// Ignore invalid UTF-8 characters in PHP 7.2+
if ( version_compare( phpversion(), '7.2', '<' ) ) {
$output = json_encode( $output );
}
else {
$output = json_encode( $output, JSON_INVALID_UTF8_IGNORE );
}
echo apply_filters( 'facetwp_ajax_response', $output, [
'data' => $data
] );
exit;
}
/**
* Resume stalled indexer
*/
function resume_index() {
$touch = (int) FWP()->indexer->get_transient( 'touch' );
if ( 0 < $touch && $_POST['touch'] == $touch ) {
FWP()->indexer->index();
}
exit;
}
}

View File

@@ -0,0 +1,839 @@
<?php
class FacetWP_Builder
{
public $css = [];
public $data = [];
public $custom_css;
function __construct() {
add_filter( 'facetwp_query_args', [ $this, 'hydrate_date_values' ], 999 );
add_filter( 'facetwp_builder_dynamic_tag_value', [ $this, 'dynamic_tag_value' ], 0, 3 );
}
/**
* Generate CSS class string (helper method)
* @since 3.9.3
*/
function get_classes( $type, $settings ) {
$classes = [ $type, $settings['name'], $settings['css_class'] ];
return trim( implode( ' ', $classes ) );
}
/**
* Generate the layout HTML
* @since 3.2.0
*/
function render_layout( $layout ) {
global $wp_query, $post;
$counter = 0;
$settings = $layout['settings'];
$this->custom_css = $settings['custom_css'];
$selector = '.fwpl-layout';
$selector .= empty( $settings['name'] ) ? '' : '.' . $settings['name'];
$this->css = [
'.fwpl-layout, .fwpl-row' => [
'display' => 'grid'
],
$selector => [
'grid-template-columns' => 'repeat(' . $settings['num_columns'] . ', 1fr)',
'grid-gap' => $settings['grid_gap'] . 'px'
],
$selector . ' .fwpl-result' => $this->build_styles( $settings )
];
$classes = $this->get_classes( 'fwpl-layout', $settings );
$output = '<div class="' . $classes . '">';
if ( have_posts() ) {
while ( have_posts() ) : the_post();
$counter++;
// Default dynamic tags
$tags = [
'post:id' => $post->ID,
'post:name' => $post->post_name,
'post:type' => $post->post_type,
'post:title' => $post->post_title,
'post:url' => get_permalink()
];
$params = [
'layout' => $layout,
'post' => $post
];
$this->data = apply_filters( 'facetwp_builder_dynamic_tags', $tags, $params );
$output .= '<div class="fwpl-result r' . $counter . '">';
foreach ( $layout['items'] as $row ) {
$output .= $this->render_row( $row );
}
$output .= '</div>';
$output = $this->parse_dynamic_tags( $output, $params );
endwhile;
}
else {
$no_results_text = $settings['no_results_text'] ?? '';
$output .= do_shortcode( $no_results_text );
}
$output .= '</div>';
$output .= $this->render_css();
return $output;
}
/**
* Generate the row HTML
* @since 3.2.0
*/
function render_row( $row ) {
$settings = $row['settings'];
$this->css['.fwpl-row.' . $settings['name'] ] = $this->build_styles( $settings );
$classes = $this->get_classes( 'fwpl-row', $settings );
$output = '<div class="' . $classes . '">';
foreach ( $row['items'] as $col ) {
$output .= $this->render_col( $col );
}
$output .= '</div>';
return $output;
}
/**
* Generate the col HTML
* @since 3.2.0
*/
function render_col( $col ) {
$settings = $col['settings'];
$this->css['.fwpl-col.' . $settings['name'] ] = $this->build_styles( $settings );
$classes = $this->get_classes( 'fwpl-col', $settings );
$output = '<div class="fwpl-col ' . $classes . '">';
foreach ( $col['items'] as $item ) {
if ( 'row' == $item['type'] ) {
$output .= $this->render_row( $item );
}
elseif ( 'item' == $item['type'] ) {
$output .= $this->render_item( $item );
}
}
$output .= '</div>';
return $output;
}
/**
* Generate the item HTML
* @since 3.2.0
*/
function render_item( $item ) {
global $post;
$settings = $item['settings'];
$name = $settings['name'];
$source = $item['source'];
$value = $source;
$selector = '.fwpl-item.' . $name;
$selector = ( 'button' == $source ) ? $selector . ' button' : $selector;
$this->css[ $selector ] = $this->build_styles( $settings );
if ( 0 === strpos( $source, 'post_' ) || 'ID' == $source ) {
if ( 'post_title' == $source ) {
$value = $this->linkify( $post->$source, $settings['link'] );
}
elseif ( 'post_excerpt' == $source ) {
$value = get_the_excerpt( $post->ID );
}
elseif ( 'post_content' == $source ) {
$value = apply_filters( 'the_content', $post->post_content );
}
elseif ( 'post_author' == $source ) {
$field = $settings['author_field'];
$user = get_user_by( 'id', $post->$source );
$value = $user->$field;
}
elseif ( 'post_type' == $source ) {
$pt_obj = get_post_type_object( $post->$source );
$value = $pt_obj->labels->singular_name ?? $post->$source;
}
else {
$value = $post->$source;
}
}
elseif ( 0 === strpos( $source, 'cf/' ) ) {
$value = get_post_meta( $post->ID, substr( $source, 3 ), true );
$value = $this->linkify( $value, $settings['link'] );
}
elseif ( 0 === strpos( $source, 'tax/' ) ) {
$temp = [];
$taxonomy = substr( $source, 4 );
$terms = get_the_terms( $post->ID, $taxonomy );
if ( is_array( $terms ) ) {
foreach ( $terms as $term_obj ) {
$term = $this->linkify( $term_obj->name, $settings['term_link'], [
'term_id' => $term_obj->term_id,
'taxonomy' => $taxonomy
] );
$temp[] = '<span class="fwpl-term fwpl-term-' . $term_obj->slug . ' fwpl-tax-' . $taxonomy . '">' . $term . '</span>';
}
}
$value = implode( $settings['separator'], $temp );
}
elseif ( 0 === strpos( $source, 'woo/' ) ) {
$field = substr( $source, 4 );
$product = wc_get_product( $post->ID );
// Invalid product
if ( ! is_object( $product ) ) {
$value = '';
}
// Price
elseif ( 'price' == $field || 'sale_price' == $field || 'regular_price' == $field ) {
if ( $product->is_type( 'variable' ) ) {
$method_name = "get_variation_$field";
$value = $product->$method_name( 'min' ); // get_variation_price()
}
else {
$method_name = "get_$field";
$value = $product->$method_name(); // get_price()
}
}
// Average Rating
elseif ( 'average_rating' == $field ) {
$value = $product->get_average_rating();
}
// Stock Status
elseif ( 'stock_status' == $field ) {
$value = $product->is_in_stock() ? __( 'In Stock', 'fwp' ) : __( 'Out of Stock', 'fwp' );
}
// On Sale
elseif ( 'on_sale' == $field ) {
$value = $product->is_on_sale() ? __( 'On Sale', 'fwp' ) : '';
}
// Product Type
elseif ( 'product_type' == $field ) {
$value = $product->get_type();
}
}
elseif ( 0 === strpos( $source, 'acf/' ) && isset( FWP()->acf ) ) {
$value = FWP()->acf->get_field( $source, $post->ID );
}
elseif ( 'featured_image' == $source ) {
$value = get_the_post_thumbnail( $post->ID, $settings['image_size'] );
$value = $this->linkify( $value, $settings['link'] );
}
elseif ( 'button' == $source ) {
$value = '<button>' . $settings['button_text'] . '</button>';
$value = $this->linkify( $value, $settings['link'] );
}
elseif ( 'html' == $source ) {
$value = do_shortcode( $settings['content'] );
}
// Date format
if ( ! empty( $settings['date_format'] ) && ! empty( $value ) ) {
if ( ! empty( $settings['input_format'] ) ) {
$date = DateTime::createFromFormat( $settings['input_format'], $value );
}
else {
$date = new DateTime( $value );
}
// Use wp_date() to support i18n
if ( $date ) {
$value = wp_date( $settings['date_format'], $date->getTimestamp(), new DateTimeZone( 'UTC' ) );
}
}
// Number format
if ( ! empty( $settings['number_format'] ) && ! empty( $value ) ) {
$decimals = 2;
$format = $settings['number_format'];
$decimal_sep = FWP()->helper->get_setting( 'decimal_separator' );
$thousands_sep = FWP()->helper->get_setting( 'thousands_separator' );
// No thousands separator
if ( false === strpos( $format, ',' ) ) {
$thousands_sep = '';
}
// Handle decimals
if ( false === ( $pos = strpos( $format, '.' ) ) ) {
$decimals = 0;
}
else {
$decimals = strlen( $format ) - $pos - 1;
}
$value = number_format( $value, $decimals, $decimal_sep, $thousands_sep );
}
$output = '';
$prefix = $settings['prefix'] ?? '';
$suffix = $settings['suffix'] ?? '';
// Allow value hooks
$value = apply_filters( 'facetwp_builder_item_value', $value, $item );
// Convert array to string
if ( is_array( $value ) ) {
$value = implode( ', ', $value );
}
// Store the RAW short-tag
$this->data[ "$name:raw" ] = $value;
// Attach the prefix / suffix to the value
if ( '' != $value ) {
$value = $prefix . $value . $suffix;
}
// Store the short-tag
$this->data[ $name ] = $value;
// Build the list of CSS classes
$classes = $this->get_classes( 'fwpl-item', $settings );
if ( '' == $value ) {
$classes .= ' is-empty';
}
// Prevent output
if ( ! $settings['is_hidden'] ) {
$output = '<div class="' . $classes . '">' . $value . '</div>';
}
return $output;
}
/**
* Parse dynamic tags, e.g. {{ first_name }}
*/
function parse_dynamic_tags( $output, $params ) {
$pattern = '/({{[ ]?(.*?)[ ]?}})/s';
return preg_replace_callback( $pattern, function( $matches ) use( $params ) {
$tag_name = $matches[2];
$tag_value = $this->data[ $tag_name ] ?? '';
return apply_filters( 'facetwp_builder_dynamic_tag_value', $tag_value, $tag_name, $params );
}, $output );
}
/**
* Calculate some dynamic tag values on-the-fly, to prevent
* unnecessary queries and extra load time
*/
function dynamic_tag_value( $tag_value, $tag_name, $params ) {
if ( 'post:image' == $tag_name ) {
$tag_value = get_the_post_thumbnail_url( $params['post']->ID, 'full' );
}
return $tag_value;
}
/**
* Build the redundant styles (border, padding,etc)
* @since 3.2.0
*/
function build_styles( $settings ) {
$styles = [];
if ( isset( $settings['grid_template_columns'] ) ) {
$styles['grid-template-columns'] = $settings['grid_template_columns'];
}
if ( isset( $settings['border'] ) ) {
$styles['border-style'] = $settings['border']['style'];
$styles['border-color'] = $settings['border']['color'];
$styles['border-width'] = $this->get_widths( $settings['border']['width'] );
}
if ( isset( $settings['background_color'] ) ) {
$styles['background-color'] = $settings['background_color'];
}
if ( isset( $settings['padding'] ) ) {
$styles['padding'] = $this->get_widths( $settings['padding'] );
}
if ( isset( $settings['text_style'] ) ) {
$styles['text-align'] = $settings['text_style']['align'];
$styles['font-weight'] = $settings['text_style']['bold'] ? 'bold' : '';
$styles['font-style'] = $settings['text_style']['italic'] ? 'italic' : '';
}
if ( isset( $settings['font_size'] ) ) {
$styles['font-size'] = $settings['font_size']['size'] . $settings['font_size']['unit'];
}
if ( isset( $settings['text_color'] ) ) {
$styles['color'] = $settings['text_color'];
}
if ( isset( $settings['button_border'] ) ) {
$border = $settings['button_border'];
$width = $border['width'];
$unit = $width['unit'];
$styles['color'] = $settings['button_text_color'];
$styles['background-color'] = $settings['button_color'];
$styles['padding'] = $this->get_widths( $settings['button_padding'] );
$styles['border-style'] = $border['style'];
$styles['border-color'] = $border['color'];
$styles['border-top-width'] = $width['top'] . $unit;
$styles['border-right-width'] = $width['right'] . $unit;
$styles['border-bottom-width'] = $width['bottom'] . $unit;
$styles['border-left-width'] = $width['left'] . $unit;
}
return $styles;
}
/**
* Build the CSS widths, e.g. for "padding" or "border-width"
* @since 3.2.0
*/
function get_widths( $data ) {
$unit = $data['unit'];
$top = $data['top'];
$right = $data['right'];
$bottom = $data['bottom'];
$left = $data['left'];
if ( $top == $right && $right == $bottom && $bottom == $left ) {
return "$top$unit";
}
elseif ( $top == $bottom && $left == $right ) {
return "$top$unit $left$unit";
}
return "$top$unit $right$unit $bottom$unit $left$unit";
}
/**
* Convert a value into a link
* @since 3.2.0
*/
function linkify( $value, $link_data, $term_data = [] ) {
global $post;
$type = $link_data['type'];
$href = $link_data['href'];
$target = $link_data['target'];
if ( 'none' !== $type ) {
if ( 'post' == $type ) {
$href = get_permalink();
}
if ( 'term' == $type ) {
$href = get_term_link( $term_data['term_id'], $term_data['taxonomy'] );
}
if ( ! empty( $target ) ) {
$target = ' target="' . $target . '"';
}
$value = '<a href="' . $href . '"' . $target . '>' . $value . '</a>';
}
return $value;
}
/**
* Turn the CSS array into valid CSS
* @since 3.2.0
*/
function render_css() {
$output = "\n<style>\n";
foreach ( $this->css as $selector => $props ) {
$valid_rules = $this->get_valid_css_rules( $props );
if ( ! empty( $valid_rules ) ) {
$output .= $selector . " {\n";
foreach ( $valid_rules as $prop => $value ) {
$output .= " $prop: $value;\n";
}
$output .= "}\n";
}
}
if ( ! empty( $this->custom_css ) ) {
$output .= $this->custom_css . "\n";
}
$output .= "</style>\n";
return $output;
}
/**
* Filter out empty or invalid rules
* @since 3.2.0
*/
function get_valid_css_rules( $props ) {
$rules = [];
foreach ( $props as $prop => $value ) {
if ( $this->is_valid_css_rule( $prop, $value ) ) {
$rules[ $prop ] = $value;
}
}
return $rules;
}
/**
* Optimize CSS rules
* @since 3.2.0
*/
function is_valid_css_rule( $prop, $value ) {
$return = true;
if ( empty( $value ) || 'px' === $value || '0px' === $value || 'none' === $value ) {
$return = false;
}
if ( 'font-size' === $prop && '0px' === $value ) {
$return = false;
}
return $return;
}
/**
* Make sure the query is valid
* @since 3.2.0
*/
function parse_query_obj( $query_obj ) {
$output = [];
$tax_query = [];
$meta_query = [];
$date_query = [];
$post_type = 'any';
$post_status = [ 'publish' ];
$posts_per_page = 10;
$post_in = [];
$post_not_in = [];
$author_in = [];
$author_not_in = [];
$orderby = [];
if ( ! empty( $query_obj['posts_per_page'] ) ) {
$posts_per_page = (int) $query_obj['posts_per_page'];
}
if ( ! empty( $query_obj['post_type'] ) ) {
$post_type = array_column( $query_obj['post_type'], 'value' );
}
if ( empty( $query_obj['filters'] ) ) {
$query_obj['filters'] = [];
}
if ( empty( $query_obj['orderby'] ) ) {
$query_obj['orderby'] = [];
}
foreach ( $query_obj['filters'] as $filter ) {
$key = $filter['key'];
$value = $filter['value'];
$compare = $filter['compare'];
$type = $filter['type'];
// Cast as decimal for more accuracy
$type = ( 'NUMERIC' == $type ) ? 'DECIMAL(16,4)' : $type;
$exists_bypass = false;
$value_bypass = false;
// Clear the value for certain compare types
if ( in_array( $compare, [ 'EXISTS', 'NOT EXISTS', 'EMPTY', 'NOT EMPTY' ] ) ) {
$value_bypass = true;
$value = '';
}
if ( in_array( $compare, [ 'EXISTS', 'NOT EXISTS' ] ) ) {
$exists_bypass = true;
}
// If "EMPTY", use "=" compare type w/ empty string value
if ( in_array( $compare, [ 'EMPTY', 'NOT EMPTY' ] ) ) {
$compare = ( 'EMPTY' == $compare ) ? '=' : '!=';
}
// Handle multiple values
if ( is_array( $value ) ) {
if ( in_array( $compare, [ '=', '!=' ] ) ) {
$compare = ( '=' == $compare ) ? 'IN' : 'NOT IN';
}
if ( ! in_array( $compare, [ 'IN', 'NOT IN' ] ) ) {
$value = $value[0];
}
}
if ( empty( $value ) && ! $value_bypass ) {
continue;
}
// Support dynamic URL vars
$value = $this->parse_uri_tags( $value );
// Prepend with "date|" so we can populate with hydrate_date_values()
if ( 'DATE' == $type ) {
$value = 'date|' . $value;
}
if ( 'ID' == $key ) {
$arg_name = ( 'IN' == $compare ) ? 'post_in' : 'post_not_in';
$$arg_name = $value;
}
elseif ( 'post_author' == $key ) {
$arg_name = ( 'IN' == $compare ) ? 'author_in' : 'author_not_in';
$$arg_name = $value;
}
elseif ( 'post_status' == $key ) {
$post_status = $value;
}
elseif ( 'post_date' == $key || 'post_modified' == $key ) {
if ( '>' == $compare || '>=' == $compare ) {
$date_query[] = [
'after' => $value,
'inclusive' => ( '>=' == $compare )
];
}
if ( '<' == $compare || '<=' == $compare ) {
$date_query[] = [
'before' => $value,
'inclusive' => ( '<=' == $compare )
];
}
}
elseif ( 0 === strpos( $key, 'tax/' ) ) {
$temp = [
'taxonomy' => substr( $key, 4 ),
'field' => 'slug',
'operator' => $compare
];
if ( ! $exists_bypass ) {
$temp['terms'] = $value;
}
$tax_query[] = $temp;
}
else {
$temp = [
'key' => substr( $key, strpos( $key, '/' ) + 1 ),
'compare' => $compare,
'type' => $type
];
if ( ! $exists_bypass ) {
$temp['value'] = $value;
}
$meta_query[] = $temp;
}
}
foreach ( $query_obj['orderby'] as $index => $data ) {
if ( 'cf/' == substr( $data['key'], 0, 3 ) ) {
$type = $data['type'];
// Cast as decimal for more accuracy
$type = ( 'NUMERIC' == $type ) ? 'DECIMAL(16,4)' : $type;
$meta_query['sort_' . $index] = [
'key' => substr( $data['key'], 3 ),
'type' => $type
];
$orderby['sort_' . $index] = $data['order'];
}
else {
$orderby[ $data['key'] ] = $data['order'];
}
}
$temp = [
'post_type' => $post_type,
'post_status' => $post_status,
'meta_query' => $meta_query,
'tax_query' => $tax_query,
'date_query' => $date_query,
'post__in' => $post_in,
'post__not_in' => $post_not_in,
'author__in' => $author_in,
'author__not_in' => $author_not_in,
'orderby' => $orderby,
'posts_per_page' => $posts_per_page
];
foreach ( $temp as $key => $val ) {
if ( ! empty( $val ) ) {
$output[ $key ] = $val;
}
}
return $output;
}
/**
* Get necessary values for the layout builder
* @since 3.2.0
*/
function get_layout_data() {
$sources = FWP()->helper->get_data_sources();
unset( $sources['post'] );
// Static options
$output = [
'row' => 'Child Row',
'html' => 'HTML',
'button' => 'Button',
'featured_image' => 'Featured Image',
'ID' => 'Post ID',
'post_title' => 'Post Title',
'post_name' => 'Post Name',
'post_content' => 'Post Content',
'post_excerpt' => 'Post Excerpt',
'post_date' => 'Post Date',
'post_modified' => 'Post Modified',
'post_author' => 'Post Author',
'post_type' => 'Post Type'
];
foreach ( $sources as $group ) {
foreach ( $group['choices'] as $name => $label ) {
$output[ $name ] = $label;
}
}
return $output;
}
/**
* Get necessary data for the query builder
* @since 3.0.0
*/
function get_query_data() {
$builder_post_types = [];
$post_types = get_post_types( [ 'public' => true ], 'objects' );
$data_sources = FWP()->helper->get_data_sources( 'builder' );
// Remove ACF choices
unset( $data_sources['acf'] );
foreach ( $post_types as $type ) {
$builder_post_types[] = [
'label' => $type->labels->name,
'value' => $type->name
];
}
$data_sources['posts']['choices'] = [
'ID' => 'ID',
'post_author' => 'Post Author',
'post_status' => 'Post Status',
'post_date' => 'Post Date',
'post_modified' => 'Post Modified'
];
return apply_filters( 'facetwp_builder_query_data', [
'post_types' => $builder_post_types,
'filter_by' => $data_sources
] );
}
/**
* Replace "date|" placeholders with actual dates
*/
function hydrate_date_values( $query_args ) {
if ( isset( $query_args['meta_query'] ) ) {
foreach ( $query_args['meta_query'] as $index => $row ) {
if ( isset( $row['value'] ) && is_string( $row['value'] ) && 0 === strpos( $row['value'], 'date|' ) ) {
$value = trim( substr( $row['value'], 5 ) );
$value = date( 'Y-m-d', strtotime( $value ) );
$query_args['meta_query'][ $index ]['value'] = $value;
}
}
}
return $query_args;
}
/**
* Let users pull URI or GET params into the query builder
* E.g. "http:uri", "http:uri:0", or "http:get:year"
* @since 3.6.0
*/
function parse_uri_tags( $values ) {
$temp = (array) $values;
foreach ( $temp as $key => $value ) {
if ( 0 === strpos( $value, 'http:uri' ) ) {
$uri = FWP()->helper->get_uri();
$uri_parts = explode( '/', $uri );
$tag_parts = explode( ':', $value );
if ( isset( $tag_parts[2] ) ) {
$index = (int) $tag_parts[2];
$index = ( $index < 0 ) ? count( $uri_parts ) + $index : $index;
$temp[ $key ] = $uri_parts[ $index ] ?? '';
}
else {
$temp[ $key ] = $uri;
}
}
elseif ( 0 === strpos( $value, 'http:get:' ) ) {
$tag_parts = explode( ':', $value );
$temp[ $key ] = $_GET[ $tag_parts[2] ] ?? '';
}
}
return is_array( $values ) ? $temp : $temp[0];
}
}

View File

@@ -0,0 +1,73 @@
<?php
class FacetWP_Diff
{
/**
* Compare "facetwp_settings" with "facetwp_settings_last_index" to determine
* whether the user needs to rebuild the index
* @since 3.0.9
*/
function is_reindex_needed() {
$s1 = FWP()->helper->load_settings();
$s2 = FWP()->helper->load_settings( true );
// Compare settings
$to_check = [ 'thousands_separator', 'decimal_separator', 'wc_enable_variations', 'wc_index_all' ];
foreach ( $to_check as $name ) {
$attr1 = $this->get_attr( $name, $s1['settings'] );
$attr2 = $this->get_attr( $name, $s2['settings'] );
if ( $attr1 !== $attr2 ) {
return true;
}
}
// Get facets, removing non-indexable ones
$f1 = array_filter( $s1['facets'], [ $this, 'is_indexable' ] );
$f2 = array_filter( $s2['facets'], [ $this, 'is_indexable' ] );
// The facet count is different
if ( count( $f1 ) !== count( $f2 ) ) {
return true;
}
// Sort the facets alphabetically
usort( $f1, function( $a, $b ) {
return strcmp( $a['name'], $b['name'] );
});
usort( $f2, function( $a, $b ) {
return strcmp( $a['name'], $b['name'] );
});
// Compare facet properties
$to_check = [ 'name', 'type', 'source', 'source_other', 'parent_term', 'hierarchical', 'modifier_type', 'modifier_values' ];
foreach ( $f1 as $index => $facet ) {
foreach ( $to_check as $attr ) {
$attr1 = $this->get_attr( $attr, $facet );
$attr2 = $this->get_attr( $attr, $f2[ $index ] );
if ( $attr1 !== $attr2 ) {
return true;
}
}
}
return false;
}
function is_indexable( $facet ) {
return ! in_array( $facet['type'], [ 'search', 'pager', 'reset', 'sort' ] );
}
/**
* Get an array element
* @since 3.0.9
*/
function get_attr( $name, $collection ) {
return $collection[ $name ] ?? false;
}
}

View File

@@ -0,0 +1,244 @@
<?php
class FacetWP_Display
{
/* (array) Facet types being used on the page */
public $active_types = [];
/* (array) Facets being used on the page */
public $active_facets = [];
/* (array) Extra features used on the page */
public $active_extras = [];
/* (array) Saved shortcode attributes */
public $shortcode_atts = [];
/* (boolean) Whether to enable FacetWP for the current page */
public $load_assets = false;
/* (array) Scripts and stylesheets to enqueue */
public $assets = [];
/* (array) Data to pass to front-end JS */
public $json = [];
function __construct() {
add_filter( 'widget_text', 'do_shortcode' );
add_action( 'loop_start', [ $this, 'add_template_tag' ] );
add_action( 'loop_no_results', [ $this, 'add_template_tag' ] );
add_action( 'wp_footer', [ $this, 'front_scripts' ], 25 );
add_shortcode( 'facetwp', [ $this, 'shortcode' ] );
}
/**
* Detect the loop container if the "facetwp-template" class is missing
*/
function add_template_tag( $wp_query ) {
if ( true === $wp_query->get( 'facetwp' ) && did_action( 'wp_head' ) ) {
echo "<!--fwp-loop-->\n";
}
}
/**
* Set default values for atts
*
* Old: [facetwp template="foo" static]
* New: [facetwp template="foo" static="true"]
*/
function normalize_atts( $atts ) {
foreach ( $atts as $key => $val ) {
if ( is_int( $key ) ) {
$atts[ $val ] = true;
unset( $atts[ $key ] );
}
}
return $atts;
}
/**
* Register shortcodes
*/
function shortcode( $atts ) {
$atts = $this->normalize_atts( $atts );
$this->shortcode_atts[] = $atts;
$output = '';
if ( isset( $atts['facet'] ) ) {
$facet = FWP()->helper->get_facet_by_name( $atts['facet'] );
if ( $facet ) {
$ui = empty( $facet['ui_type'] ) ? $facet['type'] : $facet['ui_type'];
$ui_attr = empty( $facet['ui_type'] ) ? '' : ' data-ui="' . $ui . '"';
$output = '<div class="facetwp-facet facetwp-facet-' . $facet['name'] . ' facetwp-type-' . $ui . '" data-name="' . $facet['name'] . '" data-type="' . $facet['type'] . '"' . $ui_attr . '></div>';
// Build list of active facet types
$this->active_types[ $facet['type'] ] = $facet['type'];
$this->active_facets[ $facet['name'] ] = $facet['name'];
$this->load_assets = true;
}
}
elseif ( isset( $atts['template'] ) ) {
$template = FWP()->helper->get_template_by_name( $atts['template'] );
if ( $template ) {
$class_name = 'facetwp-template';
// Static template
if ( isset( $atts['static'] ) ) {
$renderer = new FacetWP_Renderer();
$renderer->template = $template;
$renderer->query_args = $renderer->get_query_args();
$renderer->query = new WP_Query( $renderer->query_args );
$html = $renderer->get_template_html();
$class_name .= '-static';
}
// Preload template (search engine visible)
else {
global $wp_query;
$temp_query = $wp_query;
$args = FWP()->request->process_preload_data( $template['name'] );
$preload_data = FWP()->facet->render( $args );
$html = $preload_data['template'];
$wp_query = $temp_query;
}
$output = '<div class="{class}" data-name="{name}">{html}</div>';
$output = str_replace( '{class}', $class_name, $output );
$output = str_replace( '{name}', $atts['template'], $output );
$output = str_replace( '{html}', $html, $output );
$this->load_assets = true;
}
}
elseif ( isset( $atts['sort'] ) ) {
$this->active_extras['sort'] = true;
$output = '<div class="facetwp-sort"></div>';
}
elseif ( isset( $atts['selections'] ) ) {
$output = '<div class="facetwp-selections"></div>';
}
elseif ( isset( $atts['counts'] ) ) {
$this->active_extras['counts'] = true;
$output = '<div class="facetwp-counts"></div>';
}
elseif ( isset( $atts['pager'] ) ) {
$this->active_extras['pager'] = true;
$output = '<div class="facetwp-pager"></div>';
}
elseif ( isset( $atts['per_page'] ) ) {
$this->active_extras['per_page'] = true;
$output = '<div class="facetwp-per-page"></div>';
}
$output = apply_filters( 'facetwp_shortcode_html', $output, $atts );
return $output;
}
/**
* Output facet scripts
*/
function front_scripts() {
// Not enqueued - front.js needs to load before front_scripts()
if ( apply_filters( 'facetwp_load_assets', $this->load_assets ) ) {
// Load CSS?
if ( apply_filters( 'facetwp_load_css', true ) ) {
$this->assets['front.css'] = FACETWP_URL . '/assets/css/front.css';
}
// Load required JS
$this->assets['front.js'] = FACETWP_URL . '/assets/js/dist/front.min.js';
// Backwards compat?
if ( apply_filters( 'facetwp_load_deprecated', false ) ) {
$this->assets['front-deprecated.js'] = FACETWP_URL . '/assets/js/src/deprecated.js';
}
// Load a11y?
$a11y = FWP()->helper->get_setting( 'load_a11y', 'no' );
$a11y_hook = apply_filters( 'facetwp_load_a11y', false );
if ( 'yes' == $a11y || $a11y_hook ) {
$this->assets['accessibility.js'] = FACETWP_URL . '/assets/js/src/accessibility.js';
$this->json['a11y'] = [
'label_page' => __( 'Go to page', 'fwp-front' ),
'label_page_next' => __( 'Go to next page', 'fwp-front' ),
'label_page_prev' => __( 'Go to previous page', 'fwp-front' )
];
}
// Pass GET and URI params
$http_params = [
'get' => $_GET,
'uri' => FWP()->helper->get_uri(),
'url_vars' => FWP()->request->url_vars,
];
// See FWP()->facet->get_query_args()
if ( ! empty( FWP()->facet->archive_args ) ) {
$http_params['archive_args'] = FWP()->facet->archive_args;
}
// Populate the FWP_JSON object
$this->json['prefix'] = FWP()->helper->get_setting( 'prefix' );
$this->json['no_results_text'] = __( 'No results found', 'fwp-front' );
$this->json['ajaxurl'] = get_rest_url() . 'facetwp/v1/refresh';
$this->json['nonce'] = wp_create_nonce( 'wp_rest' );
if ( apply_filters( 'facetwp_use_preloader', true ) ) {
$overrides = FWP()->request->process_preload_overrides([
'facets' => $this->active_facets,
'extras' => $this->active_extras,
]);
$args = FWP()->request->process_preload_data( false, $overrides );
$this->json['preload_data'] = FWP()->facet->render( $args );
}
ob_start();
foreach ( $this->active_types as $type ) {
$facet_class = FWP()->helper->facet_types[ $type ];
if ( method_exists( $facet_class, 'front_scripts' ) ) {
$facet_class->front_scripts();
}
}
$inline_scripts = ob_get_clean();
$assets = apply_filters( 'facetwp_assets', $this->assets );
foreach ( $assets as $slug => $data ) {
$data = (array) $data;
$is_css = ( 'css' == substr( $slug, -3 ) );
$version = empty( $data[1] ) ? FACETWP_VERSION : $data[1];
$url = $data[0];
if ( false !== strpos( $url, 'facetwp' ) ) {
$prefix = ( false !== strpos( $url, '?' ) ) ? '&' : '?';
$url .= $prefix . 'ver=' . $version;
}
$html = $is_css ? '<link href="{url}" rel="stylesheet">' : '<script src="{url}"></script>';
$html = apply_filters( 'facetwp_asset_html', $html, $url );
echo str_replace( '{url}', $url, $html ) . "\n";
}
echo $inline_scripts;
?>
<script>
window.FWP_JSON = <?php echo json_encode( $this->json ); ?>;
window.FWP_HTTP = <?php echo json_encode( $http_params ); ?>;
</script>
<?php
}
}
}

View File

@@ -0,0 +1,600 @@
<?php
final class FacetWP_Helper
{
/* (array) The facetwp_settings option (after hooks) */
public $settings;
/* (array) Associative array of facet objects */
public $facet_types;
/* (array) Cached data sources */
public $data_sources;
/* (array) Cached terms */
public $term_cache;
/* (array) Index table row counts */
public $row_counts;
function __construct() {
$this->facet_types = $this->get_facet_types();
$this->settings = $this->load_settings();
}
/**
* Parse the URL hostname
*/
function get_http_host() {
return parse_url( get_option( 'home' ), PHP_URL_HOST );
}
/**
* Get the current page URI
*/
function get_uri() {
if ( isset( FWP()->facet->http_params ) ) {
return FWP()->facet->http_params['uri'];
}
$uri = parse_url( $_SERVER['REQUEST_URI'] );
return isset( $uri['path'] ) ? trim( $uri['path'], '/' ) : '';
}
/**
* Get available facet types
*/
function get_facet_types() {
if ( ! empty( $this->facet_types ) ) {
return $this->facet_types;
}
include( FACETWP_DIR . '/includes/facets/base.php' );
$types = [
'checkboxes' => 'Facetwp_Facet_Checkboxes',
'dropdown' => 'Facetwp_Facet_Dropdown',
'radio' => 'Facetwp_Facet_Radio_Core',
'fselect' => 'Facetwp_Facet_fSelect',
'hierarchy' => 'Facetwp_Facet_Hierarchy',
'slider' => 'Facetwp_Facet_Slider',
'search' => 'Facetwp_Facet_Search',
'autocomplete' => 'Facetwp_Facet_Autocomplete',
'date_range' => 'Facetwp_Facet_Date_Range',
'number_range' => 'Facetwp_Facet_Number_Range',
'rating' => 'FacetWP_Facet_Rating',
'proximity' => 'Facetwp_Facet_Proximity_Core',
'pager' => 'FacetWP_Facet_Pager',
'reset' => 'FacetWP_Facet_Reset',
'sort' => 'FacetWP_Facet_Sort'
];
$facet_types = [];
foreach ( $types as $slug => $class_name ) {
include( FACETWP_DIR . "/includes/facets/$slug.php" );
$facet_types[ $slug ] = new $class_name();
}
return apply_filters( 'facetwp_facet_types', $facet_types );
}
/**
* Get settings and allow for developer hooks
*/
function load_settings( $last_index = false ) {
$name = $last_index ? 'facetwp_settings_last_index' : 'facetwp_settings';
$option = get_option( $name );
$defaults = [
'facets' => [],
'templates' => [],
'settings' => [
'thousands_separator' => ',',
'decimal_separator' => '.',
'prefix' => '_',
'load_jquery' => 'no'
]
];
$settings = ( false !== $option ) ? json_decode( $option, true ) : [];
$settings = array_merge( $defaults, $settings );
$settings['settings'] = array_merge( $defaults['settings'], $settings['settings'] );
// Store DB-based facet & template names
$db_names = [];
foreach ( $settings['facets'] as $facet ) {
$db_names[ 'facet-' . $facet['name'] ] = true;
}
foreach ( $settings['templates'] as $template ) {
$db_names[ 'template-' . $template['name'] ] = true;
}
// Programmatically registered
$facets = apply_filters( 'facetwp_facets', $settings['facets'] );
$templates = apply_filters( 'facetwp_templates', $settings['templates'] );
$tmp_facets = [];
$tmp_templates = [];
// Merge DB + code-based facets
foreach ( $facets as $facet ) {
$name = $facet['name'];
$is_db_based = isset( $db_names[ "facet-$name" ] );
if ( ! $is_db_based ) {
$facet['_code'] = true;
}
if ( ! $is_db_based || empty( $tmp_facets[ $name ] ) ) {
// Valid facet type?
if ( in_array( $facet['type'], array_keys( $this->facet_types ) ) ) {
$tmp_facets[ $name ] = $facet;
}
}
}
// Merge DB + code-based templates
foreach ( $templates as $template ) {
$name = $template['name'];
$is_db_based = isset( $db_names[ "template-$name" ] );
if ( ! $is_db_based ) {
$template['_code'] = true;
}
if ( ! $is_db_based || empty( $tmp_templates[ $name ] ) ) {
$tmp_templates[ $name ] = $template;
}
}
// Convert back to numerical arrays
$settings['facets'] = array_values( $tmp_facets );
$settings['templates'] = array_values( $tmp_templates );
// Filtered settings
return $settings;
}
/**
* Get a general setting value
*
* @param string $name The setting name
* @param mixed $default The default value
* @since 1.9
*/
function get_setting( $name, $default = '' ) {
return $this->settings['settings'][ $name ] ?? $default;
}
/**
* Get an array of all facets
* @return array
*/
function get_facets() {
return $this->settings['facets'];
}
/**
* Get an array of all templates
* @return array
*/
function get_templates() {
return $this->settings['templates'];
}
/**
* Get all properties for a single facet
* @param string $facet_name
* @return mixed An array of facet info, or false
*/
function get_facet_by_name( $facet_name ) {
foreach ( $this->get_facets() as $facet ) {
if ( $facet_name == $facet['name'] ) {
return $facet;
}
}
return false;
}
/**
* Get all properties for a single template
*
* @param string $template_name
* @return mixed An array of template info, or false
*/
function get_template_by_name( $template_name ) {
foreach ( $this->get_templates() as $template ) {
if ( $template_name == $template['name'] ) {
return $template;
}
}
return false;
}
/**
* Fetch facets using one of its settings
* @param string $setting_name
* @param mixed $setting_value
* @return array
*/
function get_facets_by( $setting, $value ) {
$matches = [];
foreach ( $this->get_facets() as $facet ) {
if ( isset( $facet[ $setting ] ) && $value === $facet[ $setting ] ) {
$matches[] = $facet;
}
}
return $matches;
}
/**
* Get terms across all languages (thanks, WPML)
* @since 3.8.5
*/
function get_terms( $taxonomy ) {
global $wpdb;
$sql = "
SELECT t.term_id, t.name, t.slug, tt.parent FROM {$wpdb->term_taxonomy} tt
INNER JOIN {$wpdb->terms} t ON t.term_id = tt.term_id
WHERE tt.taxonomy = %s";
return $wpdb->get_results( $wpdb->prepare( $sql, $taxonomy ) );
}
/**
* Get an array of term information, including depth
* @param string $taxonomy The taxonomy name
* @return array Term information
* @since 0.9.0
*/
function get_term_depths( $taxonomy ) {
if ( isset( $this->term_cache[ $taxonomy ] ) ) {
return $this->term_cache[ $taxonomy ];
}
$output = [];
$parents = [];
$terms = $this->get_terms( $taxonomy );
// Get term parents
foreach ( $terms as $term ) {
$parents[ $term->term_id ] = $term->parent;
}
// Build the term array
foreach ( $terms as $term ) {
$output[ $term->term_id ] = [
'term_id' => $term->term_id,
'name' => $term->name,
'slug' => $term->slug,
'parent_id' => $term->parent,
'depth' => 0
];
$current_parent = $term->parent;
while ( 0 < (int) $current_parent ) {
$current_parent = $parents[ $current_parent ];
$output[ $term->term_id ]['depth']++;
// Prevent an infinite loop
if ( 50 < $output[ $term->term_id ]['depth'] ) {
break;
}
}
}
$this->term_cache[ $taxonomy ] = $output;
return $output;
}
/**
* Finish sorting the facet values
* The results are already sorted by depth and (name OR count), we just need
* to move the children directly below their parents
*/
function sort_taxonomy_values( $values = [], $orderby = 'count' ) {
$final = [];
$cache = [];
// Create an "order" sort value based on the top-level items
foreach ( $values as $key => $val ) {
if ( 0 == $val['depth'] ) {
$val['order'] = $key;
$cache[ $val['term_id'] ] = $key;
$final[] = $val;
}
elseif ( isset( $cache[ $val['parent_id'] ] ) ) { // skip orphans
$val['order'] = $cache[ $val['parent_id'] ] . ".$key"; // dot-separated hierarchy string
$cache[ $val['term_id'] ] = $val['order'];
$final[] = $val;
}
}
// Sort the array based on the new "order" element
// Since this is a dot-separated hierarchy string, use version_compare
usort( $final, function( $a, $b ) {
return version_compare( $a['order'], $b['order'] );
});
return $final;
}
/**
* Sanitize SQL data
* @return mixed The sanitized value(s)
* @since 3.0.7
*/
function sanitize( $input ) {
global $wpdb;
if ( is_array( $input ) ) {
$output = [];
foreach ( $input as $key => $val ) {
$output[ $key ] = $this->sanitize( $val );
}
}
else {
if ( $wpdb->dbh && $wpdb->use_mysqli ) {
$output = mysqli_real_escape_string( $wpdb->dbh, $input );
}
else {
$output = addslashes( $input );
}
}
return $output;
}
/**
* Does an active facet with the specified setting exist?
* @return boolean
* @since 1.4.0
*/
function facet_setting_exists( $setting_name, $setting_value ) {
foreach ( FWP()->facet->facets as $f ) {
if ( isset( $f[ $setting_name ] ) && $f[ $setting_name ] == $setting_value ) {
return true;
}
}
return false;
}
/**
* Does this facet have a setting with the specified value?
* @return boolean
* @since 2.3.4
*/
function facet_is( $facet, $setting_name, $setting_value ) {
if ( is_string( $facet ) ) {
$facet = $this->get_facet_by_name( $facet );
}
if ( isset( $facet[ $setting_name ] ) && $facet[ $setting_name ] == $setting_value ) {
return true;
}
return false;
}
/**
* Hash a facet value if needed
* @return string
* @since 2.1
*/
function safe_value( $value ) {
$value = remove_accents( $value );
if ( preg_match( '/[^a-z0-9_.\- ]/i', $value ) ) {
if ( ! preg_match( '/^\d{4}-(0[1-9]|1[012])-([012]\d|3[01])/', $value ) ) {
$value = md5( $value );
}
}
$value = str_replace( ' ', '-', strtolower( $value ) );
$value = preg_replace( '/[-]{2,}/', '-', $value );
$value = ( 50 < strlen( $value ) ) ? substr( $value, 0, 50 ) : $value;
return $value;
}
/**
* Properly format numbers, taking separators into account
* @return number
* @since 2.7.5
*/
function format_number( $num ) {
$sep_decimal = $this->get_setting( 'decimal_separator' );
$sep_thousands = $this->get_setting( 'thousands_separator' );
$num = str_replace( $sep_thousands, '', $num );
$num = ( ',' == $sep_decimal ) ? str_replace( ',', '.', $num ) : $num;
$num = preg_replace( '/[^0-9-.]/', '', $num );
return $num;
}
/**
* Get facet data sources
* @return array
* @since 2.2.1
*/
function get_data_sources( $context = 'default' ) {
global $wpdb;
// Cached?
if ( ! empty( $this->data_sources ) ) {
$sources = $this->data_sources;
}
else {
// Get excluded meta keys
$excluded_fields = apply_filters( 'facetwp_excluded_custom_fields', [
'_edit_last',
'_edit_lock',
] );
// Get taxonomies
$taxonomies = get_taxonomies( [], 'object' );
// Get custom fields
$meta_keys = $wpdb->get_col( "SELECT DISTINCT meta_key FROM {$wpdb->postmeta} ORDER BY meta_key" );
$custom_fields = array_diff( $meta_keys, $excluded_fields );
$sources = [
'posts' => [
'label' => __( 'Posts', 'fwp' ),
'choices' => [
'post_type' => __( 'Post Type', 'fwp' ),
'post_date' => __( 'Post Date', 'fwp' ),
'post_modified' => __( 'Post Modified', 'fwp' ),
'post_title' => __( 'Post Title', 'fwp' ),
'post_author' => __( 'Post Author', 'fwp' )
],
'weight' => 10
],
'taxonomies' => [
'label' => __( 'Taxonomies', 'fwp' ),
'choices' => [],
'weight' => 20
],
'custom_fields' => [
'label' => __( 'Custom Fields', 'fwp' ),
'choices' => [],
'weight' => 30
]
];
foreach ( $taxonomies as $tax ) {
$sources['taxonomies']['choices'][ 'tax/' . $tax->name ] = $tax->labels->name . ' (' . $tax->name . ')';
}
foreach ( $custom_fields as $cf ) {
if ( 0 !== strpos( $cf, '_oembed_' ) ) {
$sources['custom_fields']['choices'][ 'cf/' . $cf ] = $cf;
}
}
$this->data_sources = $sources;
}
$sources = apply_filters( 'facetwp_facet_sources', $sources, $context );
uasort( $sources, [ $this, 'sort_by_weight' ] );
return $sources;
}
/**
* Sort facetwp_facet_sources by weight
* @since 2.7.5
*/
function sort_by_weight( $a, $b ) {
$a['weight'] = $a['weight'] ?? 10;
$b['weight'] = $b['weight'] ?? 10;
if ( $a['weight'] == $b['weight'] ) {
return 0;
}
return ( $a['weight'] < $b['weight'] ) ? -1 : 1;
}
/**
* Get row counts for all facets
* @since 3.3.4
*/
function get_row_counts() {
if ( isset( $this->row_counts ) ) {
return $this->row_counts;
}
global $wpdb;
$output = [];
$results = $wpdb->get_results( "SELECT facet_name, COUNT(*) AS row_count FROM {$wpdb->prefix}facetwp_index GROUP BY facet_name" );
foreach ( $results as $result ) {
$output[ $result->facet_name ] = (int) $result->row_count;
}
$this->row_counts = $output;
return $output;
}
/**
* Grab the license key
* @since 3.0.3
*/
function get_license_key() {
$license_key = defined( 'FACETWP_LICENSE_KEY' ) ? FACETWP_LICENSE_KEY : get_option( 'facetwp_license' );
$license_key = apply_filters( 'facetwp_license_key', $license_key );
return sanitize_key( trim( $license_key ) );
}
/**
* Determine whether the license is active
* @since 3.3.0
*/
function is_license_active() {
return ( 'success' == $this->get_license_meta( 'status' ) );
}
/**
* Get a license meta value
* Possible keys: status, message, expiration, payment_id, price_id
* @since 3.5.3
*/
function get_license_meta( $key = 'status' ) {
$activation = get_option( 'facetwp_activation' );
if ( ! empty( $activation ) ) {
$data = json_decode( $activation, true );
if ( isset( $data[ $key ] ) ) {
return $data[ $key ];
}
}
return false;
}
}

View File

@@ -0,0 +1,691 @@
<?php
class FacetWP_Indexer
{
/* (boolean) wp_insert_post running? */
public $is_saving = false;
/* (boolean) Whether to index a single post */
public $index_all = false;
/* (int) Number of posts to index before updating progress */
public $chunk_size = 10;
/* (string) Whether a temporary table is active */
public $table;
/* (array) Facet properties for the value being indexed */
public $facet;
/* (array) Value modifiers set via the admin UI */
public $modifiers;
function __construct() {
$this->set_table( 'auto' );
$this->run_cron();
if ( apply_filters( 'facetwp_indexer_is_enabled', true ) ) {
$this->run_hooks();
}
}
/**
* Event listeners
* @since 2.8.4
*/
function run_hooks() {
add_action( 'save_post', [ $this, 'save_post' ] );
add_action( 'delete_post', [ $this, 'delete_post' ] );
add_action( 'edited_term', [ $this, 'edit_term' ], 10, 3 );
add_action( 'delete_term', [ $this, 'delete_term' ], 10, 3 );
add_action( 'set_object_terms', [ $this, 'set_object_terms' ] );
add_action( 'facetwp_indexer_cron', [ $this, 'get_progress' ] );
add_filter( 'wp_insert_post_parent', [ $this, 'is_wp_insert_post' ] );
}
/**
* Cron task
* @since 2.8.5
*/
function run_cron() {
if ( ! wp_next_scheduled( 'facetwp_indexer_cron' ) ) {
wp_schedule_single_event( time() + 300, 'facetwp_indexer_cron' );
}
}
/**
* Update the index when posts get saved
* @since 0.1.0
*/
function save_post( $post_id ) {
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
if ( false !== wp_is_post_revision( $post_id ) ) {
return;
}
if ( 'auto-draft' == get_post_status( $post_id ) ) {
return;
}
$this->index( $post_id );
$this->is_saving = false;
}
/**
* Update the index when posts get deleted
* @since 0.6.0
*/
function delete_post( $post_id ) {
global $wpdb;
$wpdb->query( "DELETE FROM {$this->table} WHERE post_id = $post_id" );
}
/**
* Update the index when terms get saved
* @since 0.6.0
*/
function edit_term( $term_id, $tt_id, $taxonomy ) {
global $wpdb;
$term = get_term( $term_id, $taxonomy );
$slug = FWP()->helper->safe_value( $term->slug );
$matches = FWP()->helper->get_facets_by( 'source', "tax/$taxonomy" );
if ( ! empty( $matches ) ) {
$facet_names = wp_list_pluck( $matches, 'name' );
$facet_names = implode( "','", esc_sql( $facet_names ) );
$wpdb->query( $wpdb->prepare( "
UPDATE {$this->table}
SET facet_value = %s, facet_display_value = %s
WHERE facet_name IN ('$facet_names') AND term_id = %d",
$slug, $term->name, $term_id
) );
}
}
/**
* Update the index when terms get deleted
* @since 0.6.0
*/
function delete_term( $term_id, $tt_id, $taxonomy ) {
global $wpdb;
$matches = FWP()->helper->get_facets_by( 'source', "tax/$taxonomy" );
if ( ! empty( $matches ) ) {
$facet_names = wp_list_pluck( $matches, 'name' );
$facet_names = implode( "','", esc_sql( $facet_names ) );
$wpdb->query( "
DELETE FROM {$this->table}
WHERE facet_name IN ('$facet_names') AND term_id = $term_id"
);
}
}
/**
* We're hijacking wp_insert_post_parent
* Prevent our set_object_terms() hook from firing within wp_insert_post
* @since 2.2.2
*/
function is_wp_insert_post( $post_parent ) {
$this->is_saving = true;
return $post_parent;
}
/**
* Support for manual taxonomy associations
* @since 0.8.0
*/
function set_object_terms( $object_id ) {
if ( ! $this->is_saving ) {
$this->index( $object_id );
}
}
/**
* Rebuild the facet index
* @param mixed $post_id The post ID (set to FALSE to re-index everything)
*/
function index( $post_id = false ) {
global $wpdb;
$this->index_all = ( false === $post_id );
// Index everything
if ( $this->index_all ) {
// Store the pre-index settings (see FacetWP_Diff)
update_option( 'facetwp_settings_last_index', get_option( 'facetwp_settings' ), 'no' );
// PHP sessions are blocking, so close if active
if ( PHP_SESSION_ACTIVE === session_status() ) {
session_write_close();
}
// Bypass the PHP timeout
ini_set( 'max_execution_time', 0 );
// Prevent multiple indexing processes
$touch = (int) $this->get_transient( 'touch' );
if ( 0 < $touch ) {
// Run only if the indexer is inactive or stalled
if ( ( time() - $touch ) < 60 ) {
exit;
}
}
else {
// Create temp table
$this->manage_temp_table( 'create' );
}
}
// Index a single post
elseif ( is_int( $post_id ) ) {
// Clear table values
$wpdb->query( "DELETE FROM {$this->table} WHERE post_id = $post_id" );
}
// Exit
else {
return;
}
// Resume indexing?
$offset = (int) ( $_POST['offset'] ?? 0 );
$attempt = (int) ( $_POST['retries'] ?? 0 );
if ( 0 < $offset ) {
$post_ids = json_decode( get_option( 'facetwp_indexing' ), true );
}
else {
$post_ids = $this->get_post_ids_to_index( $post_id );
// Store post IDs
if ( $this->index_all ) {
update_option( 'facetwp_indexing', json_encode( $post_ids ) );
}
}
// Count total posts
$num_total = count( $post_ids );
// Get all facet sources
$facets = FWP()->helper->get_facets();
// Populate an array of facet value modifiers
$this->load_value_modifiers( $facets );
foreach ( $post_ids as $counter => $post_id ) {
// Advance until we reach the offset
if ( $counter < $offset ) {
continue;
}
// Update the progress bar
if ( $this->index_all ) {
if ( 0 == ( $counter % $this->chunk_size ) ) {
$num_retries = (int) $this->get_transient( 'retries' );
// Exit if newer retries exist
if ( $attempt < $num_retries ) {
exit;
}
// Exit if the indexer was cancelled
wp_cache_delete( 'facetwp_indexing_cancelled', 'options' );
if ( 'yes' === get_option( 'facetwp_indexing_cancelled', 'no' ) ) {
update_option( 'facetwp_transients', '' );
update_option( 'facetwp_indexing', '' );
$this->manage_temp_table( 'delete' );
exit;
}
$transients = [
'num_indexed' => $counter,
'num_total' => $num_total,
'retries' => $attempt,
'touch' => time(),
];
update_option( 'facetwp_transients', json_encode( $transients ) );
}
}
// If the indexer stalled, start from the last valid chunk
if ( 0 < $offset && ( $counter - $offset < $this->chunk_size ) ) {
$wpdb->query( "DELETE FROM {$this->table} WHERE post_id = $post_id" );
}
$this->index_post( $post_id, $facets );
}
// Indexing complete
if ( $this->index_all ) {
update_option( 'facetwp_last_indexed', time(), 'no' );
update_option( 'facetwp_transients', '', 'no' );
update_option( 'facetwp_indexing', '', 'no' );
$this->manage_temp_table( 'replace' );
$this->manage_temp_table( 'delete' );
}
do_action( 'facetwp_indexer_complete' );
}
/**
* Get an array of post IDs to index
* @since 3.6.8
*/
function get_post_ids_to_index( $post_id = false ) {
$args = [
'post_type' => 'any',
'post_status' => 'publish',
'posts_per_page' => -1,
'fields' => 'ids',
'orderby' => 'ID',
'cache_results' => false,
'no_found_rows' => true,
];
if ( is_int( $post_id ) ) {
$args['p'] = $post_id;
$args['posts_per_page'] = 1;
}
$args = apply_filters( 'facetwp_indexer_query_args', $args );
$query = new WP_Query( $args );
return (array) $query->posts;
}
/**
* Index an individual post
* @since 3.6.8
*/
function index_post( $post_id, $facets ) {
// Force WPML to change the language
do_action( 'facetwp_indexer_post', [ 'post_id' => $post_id ] );
// Loop through all facets
foreach ( $facets as $facet ) {
// Do not index search facets
if ( 'search' == $facet['type'] ) {
continue;
}
$this->facet = $facet;
$source = $facet['source'] ?? '';
// Set default index_row() params
$defaults = [
'post_id' => $post_id,
'facet_name' => $facet['name'],
'facet_source' => $source,
'facet_value' => '',
'facet_display_value' => '',
'term_id' => 0,
'parent_id' => 0,
'depth' => 0,
'variation_id' => 0,
];
$defaults = apply_filters( 'facetwp_indexer_post_facet_defaults', $defaults, [
'facet' => $facet
] );
// Set flag for custom handling
$this->is_overridden = true;
// Bypass default indexing
$bypass = apply_filters( 'facetwp_indexer_post_facet', false, [
'defaults' => $defaults,
'facet' => $facet
] );
if ( $bypass ) {
continue;
}
$this->is_overridden = false;
// Get rows to insert
$rows = $this->get_row_data( $defaults );
foreach ( $rows as $row ) {
$this->index_row( $row );
}
}
}
/**
* Get data for a table row
* @since 2.1.1
*/
function get_row_data( $defaults ) {
$output = [];
$facet = $this->facet;
$post_id = $defaults['post_id'];
$source = $defaults['facet_source'];
if ( 'tax/' == substr( $source, 0, 4 ) ) {
$used_terms = [];
$taxonomy = substr( $source, 4 );
$term_objects = wp_get_object_terms( $post_id, $taxonomy );
if ( is_wp_error( $term_objects ) ) {
return $output;
}
// Store the term depths
$hierarchy = FWP()->helper->get_term_depths( $taxonomy );
// Only index child terms
$children = false;
if ( ! empty( $facet['parent_term'] ) ) {
$children = get_term_children( $facet['parent_term'], $taxonomy );
}
foreach ( $term_objects as $term ) {
// If "parent_term" is set, only index children
if ( false !== $children && ! in_array( $term->term_id, $children ) ) {
continue;
}
// Prevent duplicate terms
if ( isset( $used_terms[ $term->term_id ] ) ) {
continue;
}
$used_terms[ $term->term_id ] = true;
// Handle hierarchical taxonomies
$term_info = $hierarchy[ $term->term_id ];
$depth = $term_info['depth'];
// Adjust depth if parent_term is set
if ( ! empty( $facet['parent_term'] ) ) {
if ( isset( $hierarchy[ $facet['parent_term'] ] ) ) {
$anchor = (int) $hierarchy[ $facet['parent_term'] ]['depth'] + 1;
$depth = ( $depth - $anchor );
}
}
$params = $defaults;
$params['facet_value'] = $term->slug;
$params['facet_display_value'] = $term->name;
$params['term_id'] = $term->term_id;
$params['parent_id'] = $term_info['parent_id'];
$params['depth'] = $depth;
$output[] = $params;
// Automatically index implicit parents
if ( 'hierarchy' == $facet['type'] || FWP()->helper->facet_is( $facet, 'hierarchical', 'yes' ) ) {
while ( $depth > 0 ) {
$term_id = $term_info['parent_id'];
$term_info = $hierarchy[ $term_id ];
$depth = $depth - 1;
if ( ! isset( $used_terms[ $term_id ] ) ) {
$used_terms[ $term_id ] = true;
$params = $defaults;
$params['facet_value'] = $term_info['slug'];
$params['facet_display_value'] = $term_info['name'];
$params['term_id'] = $term_id;
$params['parent_id'] = $term_info['parent_id'];
$params['depth'] = $depth;
$output[] = $params;
}
}
}
}
}
elseif ( 'cf/' == substr( $source, 0, 3 ) ) {
$source_noprefix = substr( $source, 3 );
$values = get_post_meta( $post_id, $source_noprefix, false );
foreach ( $values as $value ) {
$params = $defaults;
$params['facet_value'] = $value;
$params['facet_display_value'] = $value;
$output[] = $params;
}
}
elseif ( 'post' == substr( $source, 0, 4 ) ) {
$post = get_post( $post_id );
$value = $post->{$source};
$display_value = $value;
if ( 'post_author' == $source ) {
$user = get_user_by( 'id', $value );
$display_value = ( $user instanceof WP_User ) ? $user->display_name : $value;
}
elseif ( 'post_type' == $source ) {
$post_type = get_post_type_object( $value );
if ( isset( $post_type->labels->name ) ) {
$display_value = $post_type->labels->name;
}
}
$params = $defaults;
$params['facet_value'] = $value;
$params['facet_display_value'] = $display_value;
$output[] = $params;
}
return apply_filters( 'facetwp_indexer_row_data', $output, [
'defaults' => $defaults,
'facet' => $this->facet
] );
}
/**
* Index a facet value
* @since 0.6.0
*/
function index_row( $params ) {
// Allow for custom indexing
$params = apply_filters( 'facetwp_index_row', $params, $this );
// Allow hooks to bypass the row insertion
if ( is_array( $params ) ) {
$this->insert( $params );
}
}
/**
* Save a facet value to DB
* This can be trigged by "facetwp_index_row" to handle multiple values
* @since 1.2.5
*/
function insert( $params ) {
global $wpdb;
$value = $params['facet_value'];
$display_value = $params['facet_display_value'];
// Only accept scalar values
if ( '' === $value || ! is_scalar( $value ) ) {
return;
}
// Apply UI-based modifiers
if ( isset( $this->modifiers[ $params['facet_name'] ] ) ) {
$mod = $this->modifiers[ $params['facet_name' ] ];
$is_match = in_array( $display_value, $mod['values'] );
if ( ( 'exclude' == $mod['type'] && $is_match ) || ( 'include' == $mod['type'] && ! $is_match ) ) {
return;
}
}
$wpdb->query( $wpdb->prepare( "INSERT INTO {$this->table}
(post_id, facet_name, facet_value, facet_display_value, term_id, parent_id, depth, variation_id) VALUES (%d, %s, %s, %s, %d, %d, %d, %d)",
$params['post_id'],
$params['facet_name'],
FWP()->helper->safe_value( $value ),
$display_value,
$params['term_id'],
$params['parent_id'],
$params['depth'],
$params['variation_id']
) );
}
/**
* Get the indexing completion percentage
* @return mixed The decimal percentage, or -1
* @since 0.1.0
*/
function get_progress() {
$return = -1;
$num_indexed = (int) $this->get_transient( 'num_indexed' );
$num_total = (int) $this->get_transient( 'num_total' );
$retries = (int) $this->get_transient( 'retries' );
$touch = (int) $this->get_transient( 'touch' );
if ( 0 < $num_total ) {
// Resume a stalled indexer
if ( 60 < ( time() - $touch ) ) {
$post_data = [
'blocking' => false,
'timeout' => 0.02,
'body' => [
'action' => 'facetwp_resume_index',
'offset' => $num_indexed,
'retries' => $retries + 1,
'touch' => $touch
]
];
wp_remote_post( admin_url( 'admin-ajax.php' ), $post_data );
}
// Calculate the percent completion
if ( $num_indexed != $num_total ) {
$return = round( 100 * ( $num_indexed / $num_total ), 2 );
}
}
return $return;
}
/**
* Get indexer transient variables
* @since 1.7.8
*/
function get_transient( $name = false ) {
$transients = get_option( 'facetwp_transients' );
if ( ! empty( $transients ) ) {
$transients = json_decode( $transients, true );
if ( $name ) {
return $transients[ $name ] ?? false;
}
return $transients;
}
return false;
}
/**
* Set either the index or temp table
* @param string $table 'auto', 'index', or 'temp'
* @since 4.1.4
*/
function set_table( $table = 'auto' ) {
global $wpdb;
if ( 'auto' == $table ) {
$table = ( '' == get_option( 'facetwp_indexing', '' ) ) ? 'index' : 'temp';
}
$this->table = $wpdb->prefix . 'facetwp_' . $table;
}
/**
* Index table management
* @since 3.5
*/
function manage_temp_table( $action = 'create' ) {
global $wpdb;
$table = $wpdb->prefix . 'facetwp_index';
$temp_table = $wpdb->prefix . 'facetwp_temp';
if ( 'create' == $action ) {
$wpdb->query( "CREATE TABLE $temp_table LIKE $table" );
$this->set_table( 'temp' );
}
elseif ( 'replace' == $action ) {
$wpdb->query( "TRUNCATE TABLE $table" );
$wpdb->query( "INSERT INTO $table SELECT * FROM $temp_table" );
}
elseif ( 'delete' == $action ) {
$wpdb->query( "DROP TABLE IF EXISTS $temp_table" );
$this->set_table( 'index' );
}
}
/**
* Populate an array of facet value modifiers (defined in the admin UI)
* @since 3.5.6
*/
function load_value_modifiers( $facets ) {
$output = [];
foreach ( $facets as $facet ) {
$name = $facet['name'];
$type = empty( $facet['modifier_type'] ) ? 'off' : $facet['modifier_type'];
if ( 'include' == $type || 'exclude' == $type ) {
$temp = preg_split( '/\r\n|\r|\n/', trim( $facet['modifier_values'] ) );
$values = [];
// Compare using both original and encoded values
foreach ( $temp as $val ) {
$val = trim( $val );
$val_encoded = htmlentities( $val );
$val_decoded = html_entity_decode( $val );
$values[ $val ] = true;
$values[ $val_encoded ] = true;
$values[ $val_decoded ] = true;
}
$output[ $name ] = [ 'type' => $type, 'values' => array_keys( $values ) ];
}
}
$this->modifiers = $output;
}
}

View File

@@ -0,0 +1,157 @@
<?php
class FacetWP_Init
{
function __construct() {
add_action( 'init', [ $this, 'init' ], 20 );
add_filter( 'woocommerce_is_rest_api_request', [ $this, 'is_rest_api_request' ] );
}
/**
* Initialize classes and WP hooks
*/
function init() {
// i18n
$this->load_textdomain();
// is_plugin_active
include_once( ABSPATH . 'wp-admin/includes/plugin.php' );
$includes = [
'api/fetch',
'api/refresh',
'class-helper',
'class-ajax',
'class-request',
'class-renderer',
'class-diff',
'class-indexer',
'class-display',
'class-builder',
'class-overrides',
'class-settings',
'class-upgrade',
'functions'
];
foreach ( $includes as $inc ) {
include ( FACETWP_DIR . "/includes/$inc.php" );
}
new FacetWP_Upgrade();
new FacetWP_Overrides();
FWP()->api = new FacetWP_API_Fetch();
FWP()->helper = new FacetWP_Helper();
FWP()->facet = new FacetWP_Renderer();
FWP()->settings = new FacetWP_Settings();
FWP()->diff = new FacetWP_Diff();
FWP()->indexer = new FacetWP_Indexer();
FWP()->display = new FacetWP_Display();
FWP()->builder = new FacetWP_Builder();
FWP()->request = new FacetWP_Request();
FWP()->ajax = new FacetWP_Ajax();
// integrations
include( FACETWP_DIR . '/includes/integrations/searchwp/searchwp.php' );
include( FACETWP_DIR . '/includes/integrations/woocommerce/woocommerce.php' );
include( FACETWP_DIR . '/includes/integrations/edd/edd.php' );
include( FACETWP_DIR . '/includes/integrations/acf/acf.php' );
include( FACETWP_DIR . '/includes/integrations/wp-cli/wp-cli.php' );
include( FACETWP_DIR . '/includes/integrations/wp-rocket/wp-rocket.php' );
// update checks
include( FACETWP_DIR . '/includes/class-updater.php' );
// hooks
add_action( 'admin_menu', [ $this, 'admin_menu' ] );
add_action( 'wp_enqueue_scripts', [ $this, 'front_scripts' ] );
add_filter( 'redirect_canonical', [ $this, 'redirect_canonical' ], 10, 2 );
add_filter( 'plugin_action_links_facetwp/index.php', [ $this, 'plugin_action_links' ] );
do_action( 'facetwp_init' );
}
/**
* i18n support
*/
function load_textdomain() {
// admin-facing
load_plugin_textdomain( 'fwp' );
// front-facing
load_plugin_textdomain( 'fwp-front', false, basename( FACETWP_DIR ) . '/languages' );
}
/**
* Register the FacetWP settings page
*/
function admin_menu() {
add_options_page( 'FacetWP', 'FacetWP', 'manage_options', 'facetwp', [ $this, 'settings_page' ] );
}
/**
* Enqueue jQuery
*/
function front_scripts() {
if ( 'yes' == FWP()->helper->get_setting( 'load_jquery', 'yes' ) ) {
wp_enqueue_script( 'jquery' );
}
}
/**
* Route to the correct edit screen
*/
function settings_page() {
include( FACETWP_DIR . '/templates/page-settings.php' );
}
/**
* Prevent WP from redirecting FWP pager to /page/X
*/
function redirect_canonical( $redirect_url, $requested_url ) {
if ( false !== strpos( $redirect_url, FWP()->helper->get_setting( 'prefix' ) . 'paged' ) ) {
return false;
}
return $redirect_url;
}
/**
* Add "Settings" link to plugin listing page
*/
function plugin_action_links( $links ) {
$settings_link = admin_url( 'options-general.php?page=facetwp' );
$settings_link = '<a href=" ' . $settings_link . '">' . __( 'Settings', 'fwp' ) . '</a>';
array_unshift( $links, $settings_link );
return $links;
}
/**
* WooCommerce 3.6+ doesn't load its frontend includes for REST API requests
* We need to force-load these includes for FacetWP refreshes
* See includes() within class-woocommerce.php
*
* This code isn't within /integrations/woocommerce/ because it runs *before* init
*
* @since 3.3.10
*/
function is_rest_api_request( $request ) {
if ( false !== strpos( $_SERVER['REQUEST_URI'], 'facetwp' ) ) {
return false;
}
return $request;
}
}
$this->init = new FacetWP_Init();

View File

@@ -0,0 +1,103 @@
<?php
class FacetWP_Overrides
{
public $raw;
function __construct() {
add_filter( 'facetwp_index_row', [ $this, 'index_row' ], 5, 2 );
add_filter( 'facetwp_index_row', [ $this, 'format_numbers' ], 15, 2 );
add_filter( 'facetwp_is_main_query', [ $this, 'ignore_post_types' ], 10, 2 );
}
/**
* Indexer modifications
*/
function index_row( $params, $class ) {
if ( $class->is_overridden ) {
return $params;
}
$facet = FWP()->helper->get_facet_by_name( $params['facet_name'] );
// Store raw numbers to format later
if ( in_array( $facet['type'], [ 'number_range', 'slider' ] ) ) {
$this->raw = [
'value' => $params['facet_value'],
'label' => $params['facet_display_value']
];
}
// Support "Other data source" values
if ( ! empty( $facet['source_other'] ) ) {
$other_params = $params;
$other_params['facet_source'] = $facet['source_other'];
$rows = $class->get_row_data( $other_params );
$params['facet_display_value'] = $rows[0]['facet_display_value'];
}
return $params;
}
/**
* Make sure that numbers are properly formatted
*/
function format_numbers( $params, $class ) {
if ( empty( $this->raw ) ) {
return $params;
}
$value = $params['facet_value'];
$label = $params['facet_display_value'];
// Only format if un-altered
if ( $this->raw['value'] === $value && $this->raw['label'] === $label ) {
$params['facet_value'] = FWP()->helper->format_number( $this->raw['value'] );
$params['facet_display_value'] = FWP()->helper->format_number( $this->raw['label'] );
}
$this->raw = null;
return $params;
}
/**
* Ignore certain post types
*/
function ignore_post_types( $is_main_query, $query ) {
$blacklist = [
'acf-field',
'acf-field-group',
'advanced_ads',
'carts',
'cookielawinfo',
'edd_wish_list',
'ms_relationship',
'nav_menu_item',
'wc_user_membership',
'wp_block',
'wp_global_styles',
'wp_navigation',
'wp_template',
'wp_template_part'
];
$post_type = $query->get( 'post_type' );
if ( is_string( $post_type ) && in_array( $post_type, $blacklist ) ) {
$is_main_query = false;
}
// Ignore the "WP GDPR Compliance" plugin
if ( '[wpgdprc_access_request_form]' == $query->get( 's' ) ) {
$is_main_query = false;
}
return $is_main_query;
}
}

View File

@@ -0,0 +1,662 @@
<?php
class FacetWP_Renderer
{
/* (array) Data for the current facets */
public $facets;
/* (array) Data for the current template */
public $template;
/* (array) WP_Query arguments */
public $query_args;
/* (array) Data used to build the pager */
public $pager_args;
/* (string) MySQL WHERE clause passed to each facet */
public $where_clause = '';
/* (array) AJAX parameters passed in */
public $ajax_params;
/* (array) HTTP parameters from the original page (URI, GET) */
public $http_params;
/* (boolean) Is search active? */
public $is_search = false;
/* (boolean) Are we preloading? */
public $is_preload = false;
/* (array) Cache preloaded facet values */
public $preloaded_values;
/* (array) The final WP_Query object */
public $query;
function __construct() {
$this->facet_types = FWP()->helper->facet_types;
}
/**
* Generate the facet output
* @param array $params An array of arrays (see the FacetWP->refresh() method)
* @return array
*/
function render( $params ) {
$output = [
'facets' => [],
'template' => '',
'settings' => [],
];
// Hook params
$params = apply_filters( 'facetwp_render_params', $params );
// First ajax refresh?
$first_load = (bool) $params['first_load'];
$is_bfcache = (bool) $params['is_bfcache'];
// Initial pageload?
$this->is_preload = isset( $params['is_preload'] );
// Set the AJAX and HTTP params
$this->ajax_params = $params;
$this->http_params = $params['http_params'];
// Validate facets
$this->facets = [];
foreach ( $params['facets'] as $f ) {
$name = $f['facet_name'];
$facet = FWP()->helper->get_facet_by_name( $name );
if ( $facet ) {
// Default to "OR" mode
$facet['operator'] = $facet['operator'] ?? 'or';
// Support the "facetwp_preload_url_vars" hook
if ( $first_load && empty( $f['selected_values'] ) && ! empty( $this->http_params['url_vars'][ $name ] ) ) {
$f['selected_values'] = $this->http_params['url_vars'][ $name ];
}
// Support commas within search / autocomplete facets
if ( 'search' == $facet['type'] || 'autocomplete' == $facet['type'] ) {
$f['selected_values'] = implode( ',', (array) $f['selected_values'] );
}
$facet['selected_values'] = FWP()->helper->sanitize( $f['selected_values'] );
$this->facets[ $name ] = $facet;
}
}
// Get the template from $helper->settings
if ( 'wp' == $params['template'] ) {
$this->template = [ 'name' => 'wp' ];
$query_args = FWP()->request->query_vars ?? [];
}
else {
$this->template = FWP()->helper->get_template_by_name( $params['template'] );
$query_args = $this->get_query_args();
}
// Detect search string
if ( ! empty( $query_args['s'] ) ) {
$this->is_search = true;
}
// Run the query once (prevent duplicate queries when preloading)
if ( empty( $this->query_args ) ) {
// Support "post__in"
if ( empty( $query_args['post__in'] ) ) {
$query_args['post__in'] = [];
}
// Get the template "query" field
$query_args = apply_filters( 'facetwp_query_args', $query_args, $this );
// Pagination
$query_args['paged'] = empty( $params['paged'] ) ? 1 : (int) $params['paged'];
// Preserve SQL_CALC_FOUND_ROWS
unset( $query_args['no_found_rows'] );
// Narrow posts based on facet selections
$post_ids = $this->get_filtered_post_ids( $query_args );
// Update the SQL query
if ( ! empty( $post_ids ) ) {
if ( FWP()->is_filtered ) {
$query_args['post__in'] = $post_ids;
}
$this->where_clause = ' AND post_id IN (' . implode( ',', $post_ids ) . ')';
}
// Set the default limit
if ( empty( $query_args['posts_per_page'] ) ) {
$query_args['posts_per_page'] = (int) get_option( 'posts_per_page' );
}
// Adhere to the "per page" box
$per_page = isset( $params['extras']['per_page'] ) ? $params['extras']['per_page'] : '';
if ( ! empty( $per_page ) && 'default' != $per_page ) {
$query_args['posts_per_page'] = (int) $per_page;
}
$this->query_args = apply_filters( 'facetwp_filtered_query_args', $query_args, $this );
// Run the WP_Query
$this->query = new WP_Query( $this->query_args );
}
// Debug
if ( 'on' == FWP()->helper->get_setting( 'debug_mode', 'off' ) ) {
$output['settings']['debug'] = $this->get_debug_info();
}
// Generate the template HTML
// For performance gains, skip the template on pageload
if ( 'wp' != $this->template['name'] ) {
if ( ! $first_load || $is_bfcache || apply_filters( 'facetwp_template_force_load', false ) ) {
$output['template'] = $this->get_template_html();
}
}
// Don't render these facets
$frozen_facets = $params['frozen_facets'];
// Calculate pager args
$pager_args = [
'page' => (int) $this->query_args['paged'],
'per_page' => (int) $this->query_args['posts_per_page'],
'total_rows' => (int) $this->query->found_posts,
'total_pages' => 1,
];
if ( 0 < $pager_args['per_page'] ) {
$pager_args['total_pages'] = ceil( $pager_args['total_rows'] / $pager_args['per_page'] );
}
$pager_args = apply_filters( 'facetwp_pager_args', $pager_args, $this );
$this->pager_args = $pager_args;
// Stick the pager args into the JSON response
$output['settings']['pager'] = $pager_args;
// Display the pagination HTML
if ( isset( $params['extras']['pager'] ) ) {
$output['pager'] = $this->paginate( $pager_args );
}
// Display the "per page" HTML
if ( isset( $params['extras']['per_page'] ) ) {
$output['per_page'] = $this->get_per_page_box();
}
// Display the counts HTML
if ( isset( $params['extras']['counts'] ) ) {
$output['counts'] = $this->get_result_count( $pager_args );
}
// Not paging
if ( 0 == $params['soft_refresh'] ) {
$output['settings']['num_choices'] = [];
}
// Get facet data
foreach ( $this->facets as $facet_name => $the_facet ) {
$facet_type = $the_facet['type'];
$ui_type = empty( $the_facet['ui_type'] ) ? $facet_type : $the_facet['ui_type'];
// Invalid facet type
if ( ! isset( $this->facet_types[ $facet_type ] ) ) {
continue;
}
// Skip facets when paging
if ( 0 < $params['soft_refresh'] && 'pager' != $facet_type ) {
continue;
}
// Get facet labels
if ( 0 == $params['soft_refresh'] ) {
$output['settings']['labels'][ $facet_name ] = facetwp_i18n( $the_facet['label'] );
}
// Load all facets on back / forward button press (first_load = true)
if ( ! $first_load ) {
// Skip frozen facets
if ( isset( $frozen_facets[ $facet_name ] ) ) {
continue;
}
}
$args = [
'facet' => $the_facet,
'where_clause' => $this->where_clause,
'selected_values' => $the_facet['selected_values'],
];
// Load facet values if needed
if ( method_exists( $this->facet_types[ $facet_type ], 'load_values' ) ) {
// Grab preloaded values if available
if ( isset( $this->preloaded_values[ $facet_name ] ) ) {
$args['values'] = $this->preloaded_values[ $facet_name ];
}
else {
$args['values'] = $this->facet_types[ $facet_type ]->load_values( $args );
if ( $this->is_preload ) {
$this->preloaded_values[ $facet_name ] = $args['values'];
}
}
}
// Filter the render args
$args = apply_filters( 'facetwp_facet_render_args', $args );
// Return the number of available choices
if ( isset( $args['values'] ) ) {
$num_choices = 0;
$is_ghost = FWP()->helper->facet_is( $the_facet, 'ghosts', 'yes' );
foreach ( $args['values'] as $choice ) {
if ( isset( $choice['counter'] ) && ( 0 < $choice['counter'] || $is_ghost ) ) {
$num_choices++;
}
}
$output['settings']['num_choices'][ $facet_name ] = $num_choices;
}
// Generate the facet HTML
$html = $this->facet_types[ $ui_type ]->render( $args );
$output['facets'][ $facet_name ] = apply_filters( 'facetwp_facet_html', $html, $args );
// Return any JS settings
if ( method_exists( $this->facet_types[ $ui_type ], 'settings_js' ) ) {
$output['settings'][ $facet_name ] = $this->facet_types[ $ui_type ]->settings_js( $args );
}
// Grab num_choices for slider facets
if ( 'slider' == $the_facet['type'] ) {
$min = $output['settings'][ $facet_name ]['range']['min'];
$max = $output['settings'][ $facet_name ]['range']['max'];
$output['settings']['num_choices'][ $facet_name ] = ( $min == $max ) ? 0 : 1;
}
}
return apply_filters( 'facetwp_render_output', $output, $params );
}
/**
* Get WP_Query arguments by executing the template "query" field
* @return null
*/
function get_query_args() {
$defaults = [];
// Allow templates to piggyback archives
if ( apply_filters( 'facetwp_template_use_archive', false ) ) {
$main_query = $GLOBALS['wp_the_query'];
// Initial pageload
if ( $main_query->is_archive || $main_query->is_search ) {
if ( $main_query->is_category ) {
$defaults['cat'] = $main_query->get( 'cat' );
}
elseif ( $main_query->is_tag ) {
$defaults['tag_id'] = $main_query->get( 'tag_id' );
}
elseif ( $main_query->is_tax ) {
$defaults['taxonomy'] = $main_query->get( 'taxonomy' );
$defaults['term'] = $main_query->get( 'term' );
}
elseif ( $main_query->is_search ) {
$defaults['s'] = $main_query->get( 's' );
}
$this->archive_args = $defaults;
}
// Subsequent ajax requests
elseif ( ! empty( $this->http_params['archive_args'] ) ) {
foreach ( $this->http_params['archive_args'] as $key => $val ) {
if ( in_array( $key, [ 'cat', 'tag_id', 'taxonomy', 'term', 's' ] ) ) {
$defaults[ $key ] = $val;
}
}
}
}
// Use the query builder
if ( isset( $this->template['modes'] ) && 'visual' == $this->template['modes']['query'] ) {
$query_args = FWP()->builder->parse_query_obj( $this->template['query_obj'] );
}
else {
// remove UTF-8 non-breaking spaces
$query_args = preg_replace( "/\xC2\xA0/", ' ', $this->template['query'] );
$query_args = (array) eval( '?>' . $query_args );
}
// Merge the two arrays
return array_merge( $defaults, $query_args );
}
/**
* Get ALL post IDs for the matching query
* @return array An array of post IDs
*/
function get_filtered_post_ids( $query_args = [] ) {
if ( empty( $query_args ) ) {
$query_args = $this->query_args;
}
// Only get relevant post IDs
$args = array_merge( $query_args, [
'paged' => 1,
'posts_per_page' => -1,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
'cache_results' => false,
'no_found_rows' => true,
'nopaging' => true, // prevent "offset" issues
'facetwp' => false,
'fields' => 'ids',
] );
$query = new WP_Query( $args );
// Allow hooks to modify the default post IDs
$post_ids = apply_filters( 'facetwp_pre_filtered_post_ids', $query->posts, $this );
// Store the unfiltered post IDs
FWP()->unfiltered_post_ids = $post_ids;
foreach ( $this->facets as $facet_name => $the_facet ) {
$facet_type = $the_facet['type'];
// Stop looping
if ( empty( $post_ids ) ) {
break;
}
$matches = [];
$selected_values = $the_facet['selected_values'];
if ( empty( $selected_values ) ) {
continue;
}
// Handle each facet
if ( isset( $this->facet_types[ $facet_type ] ) ) {
$hook_params = [
'facet' => $the_facet,
'selected_values' => $selected_values,
];
// Hook to support custom filter_posts() handler
$matches = apply_filters( 'facetwp_facet_filter_posts', false, $hook_params );
if ( false === $matches ) {
$matches = $this->facet_types[ $facet_type ]->filter_posts( $hook_params );
}
}
// Skip this facet
if ( 'continue' == $matches ) {
continue;
}
// Force array
$matches = (array) $matches;
// Store post IDs per facet (needed for "OR" mode)
FWP()->or_values[ $facet_name ] = $matches;
if ( 'search' == $facet_type ) {
$this->is_search = true;
}
// For search facets, loop through $matches to set order
// For other facets, loop through $post_ids to preserve the existing order
$needles = ( 'search' == $facet_type ) ? $matches : $post_ids;
$haystack = ( 'search' == $facet_type ) ? $post_ids : $matches;
$haystack = array_flip( $haystack );
$intersected_ids = [];
foreach ( $needles as $post_id ) {
if ( isset( $haystack[ $post_id ] ) ) {
$intersected_ids[] = $post_id;
}
}
$post_ids = $intersected_ids;
}
$post_ids = apply_filters( 'facetwp_filtered_post_ids', array_values( $post_ids ), $this );
// Store the filtered post IDs
FWP()->filtered_post_ids = $post_ids;
// Set a flag for whether filtering is applied
FWP()->is_filtered = ( FWP()->filtered_post_ids !== FWP()->unfiltered_post_ids );
// Return a zero array if no matches
return empty( $post_ids ) ? [ 0 ] : $post_ids;
}
/**
* Run the template display code
* @return string (HTML)
*/
function get_template_html() {
global $post, $wp_query;
$output = apply_filters( 'facetwp_template_html', false, $this );
if ( false === $output ) {
ob_start();
// Preserve globals
$temp_post = is_object( $post ) ? clone $post : $post;
$temp_wp_query = is_object( $wp_query ) ? clone $wp_query : $wp_query;
$query = $this->query;
$wp_query = $query; // Make $query->blah() optional
if ( isset( $this->template['modes'] ) && 'visual' == $this->template['modes']['display'] ) {
echo FWP()->builder->render_layout( $this->template['layout'] );
}
else {
// Remove UTF-8 non-breaking spaces
$display_code = $this->template['template'];
$display_code = preg_replace( "/\xC2\xA0/", ' ', $display_code );
eval( '?>' . $display_code );
}
// Reset globals
$post = $temp_post;
$wp_query = $temp_wp_query;
// Store buffered output
$output = ob_get_clean();
}
$output = preg_replace( "/\xC2\xA0/", ' ', $output );
return $output;
}
/**
* Result count (1-10 of 234)
* @param array $params An array with "page", "per_page", and "total_rows"
* @return string
*/
function get_result_count( $params = [] ) {
$text_of = __( 'of', 'fwp-front' );
$page = (int) $params['page'];
$per_page = (int) $params['per_page'];
$total_rows = (int) $params['total_rows'];
if ( $per_page < $total_rows ) {
$lower = ( 1 + ( ( $page - 1 ) * $per_page ) );
$upper = ( $page * $per_page );
$upper = ( $total_rows < $upper ) ? $total_rows : $upper;
$output = "$lower-$upper $text_of $total_rows";
}
else {
$lower = ( 0 < $total_rows ) ? 1 : 0;
$upper = $total_rows;
$output = $total_rows;
}
return apply_filters( 'facetwp_result_count', $output, [
'lower' => $lower,
'upper' => $upper,
'total' => $total_rows,
] );
}
/**
* Pagination
* @param array $params An array with "page", "per_page", and "total_rows"
* @return string
*/
function paginate( $params = [] ) {
$pager_class = FWP()->helper->facet_types['pager'];
$pager_class->pager_args = $params;
$output = $pager_class->render_numbers([
'inner_size' => 2,
'dots_label' => '…',
'prev_label' => '&lt;&lt;',
'next_label' => '&gt;&gt;',
]);
return apply_filters( 'facetwp_pager_html', $output, $params );
}
/**
* "Per Page" dropdown box
* @return string
*/
function get_per_page_box() {
$pager_class = FWP()->helper->facet_types['pager'];
$pager_class->pager_args = $this->pager_args;
$options = apply_filters( 'facetwp_per_page_options', [ 10, 25, 50, 100 ] );
$output = $pager_class->render_per_page([
'default_label' => __( 'Per page', 'fwp-front' ),
'per_page_options' => implode( ',', $options )
]);
return apply_filters( 'facetwp_per_page_html', $output, [
'options' => $options
] );
}
/**
* Get debug info for the browser console
* @since 3.5.7
*/
function get_debug_info() {
$last_indexed = get_option( 'facetwp_last_indexed' );
$last_indexed = $last_indexed ? human_time_diff( $last_indexed ) : 'never';
$debug = [
'query_args' => $this->query_args,
'sql' => $this->query->request,
'facets' => $this->facets,
'template' => $this->template,
'settings' => FWP()->helper->settings['settings'],
'last_indexed' => $last_indexed,
'row_counts' => FWP()->helper->get_row_counts(),
'hooks_used' => $this->get_hooks_used()
];
// Reduce debug payload
if ( ! empty( $this->query_args['post__in'] ) ) {
$debug['query_args']['post__in_count'] = count( $this->query_args['post__in'] );
$debug['query_args']['post__in'] = array_slice( $this->query_args['post__in'], 0, 10 );
$debug['sql'] = preg_replace_callback( '/posts.ID IN \((.*?)\)/s', function( $matches ) {
$count = substr_count( $matches[1], ',' ) + 1;
return ( $count <= 10 ) ? $matches[0] : "posts.ID IN (<$count IDs>)";
}, $debug['sql'] );
}
return $debug;
}
/**
* Display the location of relevant hooks (for Debug Mode)
* @since 3.5.7
*/
function get_hooks_used() {
$relevant_hooks = [];
foreach ( $GLOBALS['wp_filter'] as $tag => $hook_data ) {
if ( 0 === strpos( $tag, 'facetwp' ) || 'pre_get_posts' == $tag ) {
foreach ( $hook_data->callbacks as $callbacks ) {
foreach ( $callbacks as $cb ) {
if ( is_string( $cb['function'] ) && false !== strpos( $cb['function'], '::' ) ) {
$cb['function'] = explode( '::', $cb['function'] );
}
if ( is_array( $cb['function'] ) ) {
$class = is_object( $cb['function'][0] ) ? get_class( $cb['function'][0] ) : $cb['function'][0];
$ref = new ReflectionMethod( $class, $cb['function'][1] );
}
elseif ( is_object( $cb['function'] ) ) {
if ( is_a( $cb['function'], 'Closure' ) ) {
$ref = new ReflectionFunction( $cb['function'] );
}
else {
$class = get_class( $cb['function'] );
$ref = new ReflectionMethod( $class, '__invoke' );
}
}
else {
$ref = new ReflectionFunction( $cb['function'] );
}
$filename = str_replace( ABSPATH, '', $ref->getFileName() );
// ignore built-in hooks
if ( false === strpos( $filename, 'plugins/facetwp' ) ) {
if ( false !== strpos( $filename, 'wp-content' ) ) {
$relevant_hooks[ $tag ][] = $filename . ':' . $ref->getStartLine();
}
}
}
}
}
}
return $relevant_hooks;
}
}

View File

@@ -0,0 +1,379 @@
<?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 );
}
}

View File

@@ -0,0 +1,646 @@
<?php
class FacetWP_Settings
{
/**
* Get the field settings array
* @since 3.0.0
*/
function get_registered_settings() {
$defaults = [
'general' => [
'label' => __( 'General', 'fwp' ),
'fields' => [
'license_key' => [
'label' => __( 'License key', 'fwp' ),
'html' => $this->get_setting_html( 'license_key' )
],
'gmaps_api_key' => [
'label' => __( 'Google Maps API key', 'fwp' ),
'html' => $this->get_setting_html( 'gmaps_api_key' )
],
'separators' => [
'label' => __( 'Separators', 'fwp' ),
'notes' => 'Enter the thousands and decimal separators, respectively',
'html' => $this->get_setting_html( 'separators' )
],
'prefix' => [
'label' => __( 'URL prefix', 'fwp' ),
'html' => $this->get_setting_html( 'prefix', 'dropdown', [
'choices' => [ 'fwp_' => 'fwp_', '_' => '_' ]
])
],
'load_jquery' => [
'label' => __( 'Load jQuery', 'fwp' ),
'notes' => 'FacetWP no longer requires jQuery, but enable if needed',
'html' => $this->get_setting_html( 'load_jquery', 'toggle', [
'true_value' => 'yes',
'false_value' => 'no'
])
],
'load_a11y' => [
'label' => __( 'Load a11y support', 'fwp' ),
'notes' => 'Improved accessibility for users with disabilities',
'html' => $this->get_setting_html( 'load_a11y', 'toggle', [
'true_value' => 'yes',
'false_value' => 'no'
])
],
'strict_query_detection' => [
'label' => __( 'Strict query detection', 'fwp' ),
'notes' => 'Enable if FacetWP auto-detects the wrong archive query',
'html' => $this->get_setting_html( 'strict_query_detection', 'toggle', [
'true_value' => 'yes',
'false_value' => 'no'
])
],
'debug_mode' => [
'label' => __( 'Debug mode', 'fwp' ),
'notes' => 'After enabling, type "FWP.settings.debug" into the browser console on your front-end facet page',
'html' => $this->get_setting_html( 'debug_mode', 'toggle', [
'true_value' => 'on',
'false_value' => 'off'
])
]
]
],
'woocommerce' => [
'label' => __( 'WooCommerce', 'fwp' ),
'fields' => [
'wc_enable_variations' => [
'label' => __( 'Support product variations?', 'fwp' ),
'notes' => __( 'Enable if your store uses variable products.', 'fwp' ),
'html' => $this->get_setting_html( 'wc_enable_variations', 'toggle' )
],
'wc_index_all' => [
'label' => __( 'Index out-of-stock products?', 'fwp' ),
'notes' => __( 'Show facet choices for out-of-stock products?', 'fwp' ),
'html' => $this->get_setting_html( 'wc_index_all', 'toggle' )
]
]
],
'backup' => [
'label' => __( 'Import / Export', 'fwp' ),
'fields' => [
'export' => [
'label' => __( 'Export', 'fwp' ),
'html' => $this->get_setting_html( 'export' )
],
'import' => [
'label' => __( 'Import', 'fwp' ),
'html' => $this->get_setting_html( 'import' )
]
]
]
];
if ( ! is_plugin_active( 'woocommerce/woocommerce.php' ) ) {
unset( $defaults['woocommerce'] );
}
if ( '_' == FWP()->helper->settings['settings']['prefix'] ) {
unset( $defaults['general']['fields']['prefix'] );
}
return apply_filters( 'facetwp_settings_admin', $defaults, $this );
}
/**
* All facet admin fields
* @since 3.9
*/
function get_registered_facet_fields() {
$settings = [
'label_any' => [
'label' => __( 'Default label', 'fwp' ),
'notes' => 'Customize the "Any" label',
'default' => __( 'Any', 'fwp' )
],
'placeholder' => [
'label' => __( 'Placeholder text', 'fwp' )
],
'parent_term' => [
'label' => __( 'Parent term', 'fwp' ),
'notes' => 'To show only child terms, enter the parent <a href="https://facetwp.com/how-to-find-a-wordpress-terms-id/" target="_blank">term ID</a>. Otherwise, leave blank.',
'show' => "facet.source.substr(0, 3) == 'tax'"
],
'hierarchical' => [
'type' => 'toggle',
'label' => __( 'Hierarchical', 'fwp' ),
'notes' => 'Is this a hierarchical taxonomy?',
'show' => "facet.source.substr(0, 3) == 'tax'"
],
'show_expanded' => [
'type' => 'toggle',
'label' => __( 'Show expanded', 'fwp' ),
'notes' => 'Should child terms be visible by default?',
'show' => "facet.hierarchical == 'yes'"
],
'multiple' => [
'type' => 'toggle',
'label' => __( 'Multi-select', 'fwp' ),
'notes' => 'Allow multiple selections?'
],
'ghosts' => [
'type' => 'alias',
'items' => [
'ghosts' => [
'type' => 'toggle',
'label' => __( 'Show ghosts', 'fwp' ),
'notes' => 'Show choices that would return zero results?'
],
'preserve_ghosts' => [
'type' => 'toggle',
'label' => __( 'Preserve ghost order', 'fwp' ),
'notes' => 'Keep ghost choices in the same order?',
'show' => "facet.ghosts == 'yes'"
]
]
],
'modifiers' => [
'type' => 'alias',
'items' => [
'modifier_type' => [
'type' => 'select',
'label' => __( 'Value modifiers', 'fwp' ),
'notes' => 'Include or exclude certain values?',
'choices' => [
'off' => __( 'Off', 'fwp' ),
'exclude' => __( 'Exclude these values', 'fwp' ),
'include' => __( 'Show only these values', 'fwp' )
]
],
'modifier_values' => [
'type' => 'textarea',
'label' => '',
'placeholder' => 'Add one value per line',
'show' => "facet.modifier_type != 'off'"
]
]
],
'operator' => [
'type' => 'select',
'label' => __( 'Facet logic', 'fwp' ),
'notes' => 'How should multiple selections affect the results?',
'choices' => [
'and' => __( 'AND (match all)', 'fwp' ),
'or' => __( 'OR (match any)', 'fwp' )
]
],
'orderby' => [
'type' => 'select',
'label' => __( 'Sort by', 'fwp' ),
'choices' => [
'count' => __( 'Highest count', 'fwp' ),
'display_value' => __( 'Display value', 'fwp' ),
'raw_value' => __( 'Raw value', 'fwp' ),
'term_order' => __( 'Term order', 'fwp' )
]
],
'count' => [
'label' => __( 'Count', 'fwp' ),
'notes' => 'The maximum number of choices to show (-1 for no limit)',
'default' => 10
],
'soft_limit' => [
'label' => __( 'Soft limit', 'fwp' ),
'notes' => 'Show a toggle link after this many choices',
'default' => 5,
'show' => "facet.hierarchical != 'yes'"
],
'source_other' => [
'label' => __( 'Other data source', 'fwp' ),
'notes' => 'Use a separate value for the upper limit?',
'html' => '<data-sources :facet="facet" setting-name="source_other"></data-sources>'
],
'compare_type' => [
'type' => 'select',
'label' => __( 'Compare type', 'fwp' ),
'notes' => "<strong>Basic</strong> - entered range surrounds the post's range<br /><strong>Enclose</strong> - entered range is fully inside the post's range<br /><strong>Intersect</strong> - entered range overlaps the post's range<br /><br />When in doubt, choose <strong>Basic</strong>",
'choices' => [
'' => __( 'Basic', 'fwp' ),
'enclose' => __( 'Enclose', 'fwp' ),
'intersect' => __( 'Intersect', 'fwp' )
]
],
'ui_type' => [
'label' => __( 'UI type', 'fwp' ),
'html' => '<ui-type :facet="facet"></ui-type>'
],
'reset_text' => [
'label' => __( 'Reset text', 'fwp' ),
'default' => 'Reset'
]
];
foreach ( FWP()->helper->facet_types as $name => $obj ) {
if ( method_exists( $obj, 'register_fields' ) ) {
$settings = array_merge( $settings, $obj->register_fields() );
}
}
return $settings;
}
/**
* Return HTML for a single facet field (supports aliases)
* @since 3.9
*/
function get_facet_field_html( $name ) {
ob_start();
$fields = FWP()->settings->get_registered_facet_fields();
if ( isset( $fields[ $name ] ) ) {
$field = $fields[ $name ];
if ( isset( $field['type'] ) && 'alias' == $field['type'] ) {
foreach ( $field['items'] as $k => $v ) {
$v['name'] = $k;
$this->render_facet_field( $v );
}
}
else {
$field['name'] = $name;
$this->render_facet_field( $field );
}
}
return ob_get_clean();
}
/**
* Render a facet field
* @since 3.9
*/
function render_facet_field( $field ) {
$name = str_replace( '_', '-', $field['name'] );
$type = $field['type'] ?? 'text';
$placeholder = $field['placeholder'] ?? '';
$show = isset( $field['show'] ) ? ' v-show="' . $field['show'] . '"' : '';
$default = isset( $field['default'] ) ? ' value="' . $field['default'] . '"' : '';
$label = empty( $field['label'] ) ? '' : $field['label'];
if ( isset( $field['notes'] ) ) {
$label = '<div class="facetwp-tooltip">' . $label . '<div class="facetwp-tooltip-content">' . $field['notes'] . '</div></div>';
}
ob_start();
if ( isset( $field['html'] ) ) {
echo $field['html'];
}
elseif ( 'text' == $type ) {
?>
<input type="text" class="facet-<?php echo $name; ?>" placeholder="<?php echo $placeholder; ?>"<?php echo $default; ?> />
<?php
}
elseif ( 'textarea' == $type ) {
?>
<textarea class="facet-<?php echo $name; ?>" placeholder="<?php echo $placeholder; ?>"></textarea>
<?php
}
elseif ( 'toggle' == $type ) {
?>
<label class="facetwp-switch">
<input type="checkbox" class="facet-<?php echo $name; ?>" true-value="yes" false-value="no" />
<span class="facetwp-slider"></span>
</label>
<?php
}
elseif ( 'select' == $type ) {
?>
<select class="facet-<?php echo $name; ?>">
<?php foreach ( $field['choices'] as $k => $v ) : ?>
<option value="<?php echo $k; ?>"><?php echo $v; ?></option>
<?php endforeach; ?>
</select>
<?php
}
$html = ob_get_clean();
?>
<div class="facetwp-row"<?php echo $show; ?>>
<div><?php echo $label; ?></div>
<div><?php echo $html; ?></div>
</div>
<?php
}
/**
* Return HTML for a setting field
* @since 3.0.0
*/
function get_setting_html( $setting_name, $field_type = 'text', $atts = [] ) {
ob_start();
if ( 'license_key' == $setting_name ) : ?>
<input type="text" class="facetwp-license" style="width:360px" value="<?php echo FWP()->helper->get_license_key(); ?>"<?php echo defined( 'FACETWP_LICENSE_KEY' ) ? ' disabled' : ''; ?> />
<div @click="activate" class="btn-normal"><?php _e( 'Activate', 'fwp' ); ?></div>
<div class="facetwp-activation-status field-notes"><?php echo $this->get_activation_status(); ?></div>
<?php elseif ( 'gmaps_api_key' == $setting_name ) : ?>
<input type="text" v-model="app.settings.gmaps_api_key" style="width:360px" />
<a href="https://developers.google.com/maps/documentation/javascript/get-api-key" target="_blank"><?php _e( 'Get an API key', 'fwp' ); ?></a>
<?php elseif ( 'separators' == $setting_name ) : ?>
Thousands:
<input type="text" v-model="app.settings.thousands_separator" style="width:30px" /> &nbsp;
Decimal:
<input type="text" v-model="app.settings.decimal_separator" style="width:30px" />
<?php elseif ( 'export' == $setting_name ) : ?>
<select class="export-items" multiple="multiple">
<?php foreach ( $this->get_export_choices() as $val => $label ) : ?>
<option value="<?php echo $val; ?>"><?php echo $label; ?></option>
<?php endforeach; ?>
</select>
<div class="btn-normal export-submit">
<?php _e( 'Export', 'fwp' ); ?>
</div>
<?php elseif ( 'import' == $setting_name ) : ?>
<div><textarea class="import-code" placeholder="<?php _e( 'Paste the import code here', 'fwp' ); ?>"></textarea></div>
<div><input type="checkbox" class="import-overwrite" /> <?php _e( 'Overwrite existing items?', 'fwp' ); ?></div>
<div style="margin-top:5px">
<div class="btn-normal import-submit"><?php _e( 'Import', 'fwp' ); ?></div>
</div>
<?php elseif ( 'dropdown' == $field_type ) : ?>
<select class="facetwp-setting" v-model="app.settings.<?php echo $setting_name; ?>">
<?php foreach ( $atts['choices'] as $val => $label ) : ?>
<option value="<?php echo $val; ?>"><?php echo $label; ?></option>
<?php endforeach; ?>
</select>
<?php elseif ( 'toggle' == $field_type ) : ?>
<?php
$true_value = $atts['true_value'] ?? 'yes';
$false_value = $atts['false_value'] ?? 'no';
?>
<label class="facetwp-switch">
<input
type="checkbox"
v-model="app.settings.<?php echo $setting_name; ?>"
true-value="<?php echo $true_value; ?>"
false-value="<?php echo $false_value; ?>"
/>
<span class="facetwp-slider"></span>
</label>
<?php endif;
return ob_get_clean();
}
/**
* Get an array of all facets and templates
* @since 3.0.0
*/
function get_export_choices() {
$export = [];
$settings = FWP()->helper->settings;
foreach ( $settings['facets'] as $facet ) {
$export['facet-' . $facet['name']] = 'Facet - ' . $facet['label'];
}
foreach ( $settings['templates'] as $template ) {
$export['template-' . $template['name']] = 'Template - '. $template['label'];
}
return $export;
}
/**
* Get the activation status
* @since 3.0.0
*/
function get_activation_status() {
$message = __( 'Not yet activated', 'fwp' );
$status = FWP()->helper->get_license_meta( 'status' );
if ( false !== $status ) {
if ( 'success' == $status ) {
$expiration = FWP()->helper->get_license_meta( 'expiration' );
$expiration = date( 'M j, Y', strtotime( $expiration ) );
$message = __( 'Valid until', 'fwp' ) . ' ' . $expiration;
}
else {
$message = FWP()->helper->get_license_meta( 'message' );
}
}
return $message;
}
/**
* Load i18n admin strings
* @since 3.2.0
*/
function get_i18n_strings() {
return [
'Number of grid columns' => __( 'Number of grid columns', 'fwp' ),
'Spacing between results' => __( 'Spacing between results', 'fwp' ),
'No results text' => __( 'No results text', 'fwp' ),
'Text style' => __( 'Text style', 'fwp' ),
'Text color' => __( 'Text color', 'fwp' ),
'Font size' => __( 'Font size', 'fwp' ),
'Background color' => __( 'Background color', 'fwp' ),
'Border' => __( 'Border', 'fwp' ),
'Border style' => __( 'Border style', 'fwp' ),
'None' => __( 'None', 'fwp' ),
'Solid' => __( 'Solid', 'fwp' ),
'Dashed' => __( 'Dashed', 'fwp' ),
'Dotted' => __( 'Dotted', 'fwp' ),
'Double' => __( 'Double', 'fwp' ),
'Border color' => __( 'Border color', 'fwp' ),
'Border width' => __( 'Border width', 'fwp' ),
'Button text' => __( 'Button text', 'fwp' ),
'Button text color' => __( 'Button text color', 'fwp' ),
'Button padding' => __( 'Button padding', 'fwp' ),
'Separator' => __( 'Separator', 'fwp' ),
'Custom CSS' => __( 'Custom CSS', 'fwp' ),
'Column widths' => __( 'Column widths', 'fwp' ),
'Content' => __( 'Content', 'fwp' ),
'Image size' => __( 'Image size', 'fwp' ),
'Author field' => __( 'Author field', 'fwp' ),
'Display name' => __( 'Display name', 'fwp' ),
'User login' => __( 'User login', 'fwp' ),
'User ID' => __( 'User ID', 'fwp' ),
'Field type' => __( 'Field type', 'fwp' ),
'Text' => __( 'Text', 'fwp' ),
'Date' => __( 'Date', 'fwp' ),
'Number' => __( 'Number', 'fwp' ),
'Date format' => __( 'Date format', 'fwp' ),
'Input format' => __( 'Input format', 'fwp' ),
'Number format' => __( 'Number format', 'fwp' ),
'Link' => __( 'Link', 'fwp' ),
'Link type' => __( 'Link type', 'fwp' ),
'Post URL' => __( 'Post URL', 'fwp' ),
'Custom URL' => __( 'Custom URL', 'fwp' ),
'Open in new tab?' => __( 'Open in new tab?', 'fwp' ),
'Prefix' => __( 'Prefix', 'fwp' ),
'Suffix' => __( 'Suffix', 'fwp' ),
'Hide item?' => __( 'Hide item?', 'fwp' ),
'Padding' => __( 'Padding', 'fwp' ),
'CSS class' => __( 'CSS class', 'fwp' ),
'Button Border' => __( 'Button border', 'fwp' ),
'Term URL' => __( 'Term URL', 'fwp' ),
'Fetch' => __( 'Fetch', 'fwp' ),
'All post types' => __( 'All post types', 'fwp' ),
'and show' => __( 'and show', 'fwp' ),
'per page' => __( 'per page', 'fwp' ),
'Sort by' => __( 'Sort by', 'fwp' ),
'Posts' => __( 'Posts', 'fwp' ),
'Post Title' => __( 'Post Title', 'fwp' ),
'Post Name' => __( 'Post Name', 'fwp' ),
'Post Type' => __( 'Post Type', 'fwp' ),
'Post Date' => __( 'Post Date', 'fwp' ),
'Post Modified' => __( 'Post Modified', 'fwp' ),
'Comment Count' => __( 'Comment Count', 'fwp' ),
'Menu Order' => __( 'Menu Order', 'fwp' ),
'Custom Fields' => __( 'Custom Fields', 'fwp' ),
'Narrow results by' => __( 'Narrow results by', 'fwp' ),
'Hit Enter' => __( 'Hit Enter', 'fwp' ),
'Add query sort' => __( 'Add query sort', 'fwp' ),
'Add query filter' => __( 'Add query filter', 'fwp' ),
'Clear' => __( 'Clear', 'fwp' ),
'Enter term slugs' => __( 'Enter term slugs', 'fwp' ),
'Enter values' => __( 'Enter values', 'fwp' ),
'Layout' => __( 'Layout', 'fwp' ),
'Content' => __( 'Content', 'fwp' ),
'Style' => __( 'Style', 'fwp' ),
'Row' => __( 'Row', 'fwp' ),
'Column' => __( 'Column', 'fwp' ),
'Start typing' => __( 'Start typing', 'fwp' ),
'Label' => __( 'Label', 'fwp' ),
'Unique name' => __( 'Unique name', 'fwp' ),
'Facet type' => __( 'Facet type', 'fwp' ),
'Copy shortcode' => __( 'Copy shortcode', 'fwp' ),
'Data source' => __( 'Data source', 'fwp' ),
'Switch to advanced mode' => __( 'Switch to advanced mode', 'fwp' ),
'Switch to visual mode' => __( 'Switch to visual mode', 'fwp' ),
'Display' => __( 'Display', 'fwp' ),
'Query' => __( 'Query', 'fwp' ),
'Help' => __( 'Help', 'fwp' ),
'Display Code' => __( 'Display Code', 'fwp' ),
'Query Arguments' => __( 'Query Arguments', 'fwp' ),
'Saving' => __( 'Saving', 'fwp' ),
'Indexing' => __( 'Indexing', 'fwp' ),
'The index table is empty' => __( 'The index table is empty', 'fwp' ),
'Indexing complete' => __( 'Indexing complete', 'fwp' ),
'Looking' => __( 'Looking', 'fwp' ),
'Purging' => __( 'Purging', 'fwp' ),
'Copied!' => __( 'Copied!', 'fwp' ),
'Press CTRL+C to copy' => __( 'Press CTRL+C to copy', 'fwp' ),
'Activating' => __( 'Activating', 'fwp' ),
'Re-index' => __( 'Re-index', 'fwp' ),
'Stop indexer' => __( 'Stop indexer', 'fwp' ),
'Loading' => __( 'Loading', 'fwp' ),
'Importing' => __( 'Importing', 'fwp' ),
'Convert to query args' => __( 'Convert to query args', 'fwp' )
];
}
/**
* Get available image sizes
* @since 3.2.7
*/
function get_image_sizes() {
global $_wp_additional_image_sizes;
$sizes = [];
$default_sizes = [ 'thumbnail', 'medium', 'medium_large', 'large', 'full' ];
foreach ( get_intermediate_image_sizes() as $size ) {
if ( in_array( $size, $default_sizes ) ) {
$sizes[ $size ]['width'] = (int) get_option( "{$size}_size_w" );
$sizes[ $size ]['height'] = (int) get_option( "{$size}_size_h" );
}
elseif ( isset( $_wp_additional_image_sizes[ $size ] ) ) {
$sizes[ $size ] = $_wp_additional_image_sizes[ $size ];
}
}
return $sizes;
}
/**
* Return an array of formatted image sizes
* @since 3.2.7
*/
function get_image_size_labels() {
$labels = [];
$sizes = $this->get_image_sizes();
foreach ( $sizes as $size => $data ) {
$height = ( 0 === $data['height'] ) ? 'w' : 'x' . $data['height'];
$label = $size . ' (' . $data['width'] . $height . ')';
$labels[ $size ] = $label;
}
$labels['full'] = __( 'full', 'fwp' );
return $labels;
}
/**
* Create SVG images (based on Font Awesome)
* @license https://fontawesome.com/license/free CC BY 4.0
* @since 3.6.5
*/
function get_svg() {
$output = [];
$icons = [
// [viewBox width, viewBox height, icon width, svg data]
"align-center" => [448, 512, 14, "M432 160H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0 256H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM108.1 96h231.81A12.09 12.09 0 0 0 352 83.9V44.09A12.09 12.09 0 0 0 339.91 32H108.1A12.09 12.09 0 0 0 96 44.09V83.9A12.1 12.1 0 0 0 108.1 96zm231.81 256A12.09 12.09 0 0 0 352 339.9v-39.81A12.09 12.09 0 0 0 339.91 288H108.1A12.09 12.09 0 0 0 96 300.09v39.81a12.1 12.1 0 0 0 12.1 12.1z"],
"align-left" => [448, 512, 14, "M12.83 352h262.34A12.82 12.82 0 0 0 288 339.17v-38.34A12.82 12.82 0 0 0 275.17 288H12.83A12.82 12.82 0 0 0 0 300.83v38.34A12.82 12.82 0 0 0 12.83 352zm0-256h262.34A12.82 12.82 0 0 0 288 83.17V44.83A12.82 12.82 0 0 0 275.17 32H12.83A12.82 12.82 0 0 0 0 44.83v38.34A12.82 12.82 0 0 0 12.83 96zM432 160H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0 256H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16z"],
"align-right" => [448, 512, 14, "M16 224h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16zm416 192H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm3.17-384H172.83A12.82 12.82 0 0 0 160 44.83v38.34A12.82 12.82 0 0 0 172.83 96h262.34A12.82 12.82 0 0 0 448 83.17V44.83A12.82 12.82 0 0 0 435.17 32zm0 256H172.83A12.82 12.82 0 0 0 160 300.83v38.34A12.82 12.82 0 0 0 172.83 352h262.34A12.82 12.82 0 0 0 448 339.17v-38.34A12.82 12.82 0 0 0 435.17 288z"],
"arrow-circle-up" => [512, 512, 16, "M8 256C8 119 119 8 256 8s248 111 248 248-111 248-248 248S8 393 8 256zm143.6 28.9l72.4-75.5V392c0 13.3 10.7 24 24 24h16c13.3 0 24-10.7 24-24V209.4l72.4 75.5c9.3 9.7 24.8 9.9 34.3.4l10.9-11c9.4-9.4 9.4-24.6 0-33.9L273 107.7c-9.4-9.4-24.6-9.4-33.9 0L106.3 240.4c-9.4 9.4-9.4 24.6 0 33.9l10.9 11c9.6 9.5 25.1 9.3 34.4-.4z"],
"bold" => [384, 512, 12, "M333.49 238a122 122 0 0 0 27-65.21C367.87 96.49 308 32 233.42 32H34a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h31.87v288H34a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h209.32c70.8 0 134.14-51.75 141-122.4 4.74-48.45-16.39-92.06-50.83-119.6zM145.66 112h87.76a48 48 0 0 1 0 96h-87.76zm87.76 288h-87.76V288h87.76a56 56 0 0 1 0 112z"],
"caret-down" => [320, 512, 10, "M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z"],
"cog" => [512, 512, 16, "M487.4 315.7l-42.6-24.6c4.3-23.2 4.3-47 0-70.2l42.6-24.6c4.9-2.8 7.1-8.6 5.5-14-11.1-35.6-30-67.8-54.7-94.6-3.8-4.1-10-5.1-14.8-2.3L380.8 110c-17.9-15.4-38.5-27.3-60.8-35.1V25.8c0-5.6-3.9-10.5-9.4-11.7-36.7-8.2-74.3-7.8-109.2 0-5.5 1.2-9.4 6.1-9.4 11.7V75c-22.2 7.9-42.8 19.8-60.8 35.1L88.7 85.5c-4.9-2.8-11-1.9-14.8 2.3-24.7 26.7-43.6 58.9-54.7 94.6-1.7 5.4.6 11.2 5.5 14L67.3 221c-4.3 23.2-4.3 47 0 70.2l-42.6 24.6c-4.9 2.8-7.1 8.6-5.5 14 11.1 35.6 30 67.8 54.7 94.6 3.8 4.1 10 5.1 14.8 2.3l42.6-24.6c17.9 15.4 38.5 27.3 60.8 35.1v49.2c0 5.6 3.9 10.5 9.4 11.7 36.7 8.2 74.3 7.8 109.2 0 5.5-1.2 9.4-6.1 9.4-11.7v-49.2c22.2-7.9 42.8-19.8 60.8-35.1l42.6 24.6c4.9 2.8 11 1.9 14.8-2.3 24.7-26.7 43.6-58.9 54.7-94.6 1.5-5.5-.7-11.3-5.6-14.1zM256 336c-44.1 0-80-35.9-80-80s35.9-80 80-80 80 35.9 80 80-35.9 80-80 80z"],
"columns" => [512, 512, 16, "M464 32H48C21.49 32 0 53.49 0 80v352c0 26.51 21.49 48 48 48h416c26.51 0 48-21.49 48-48V80c0-26.51-21.49-48-48-48zM224 416H64V160h160v256zm224 0H288V160h160v256z"],
"eye-slash" => [640, 512, 20, "M320 400c-75.85 0-137.25-58.71-142.9-133.11L72.2 185.82c-13.79 17.3-26.48 35.59-36.72 55.59a32.35 32.35 0 0 0 0 29.19C89.71 376.41 197.07 448 320 448c26.91 0 52.87-4 77.89-10.46L346 397.39a144.13 144.13 0 0 1-26 2.61zm313.82 58.1l-110.55-85.44a331.25 331.25 0 0 0 81.25-102.07 32.35 32.35 0 0 0 0-29.19C550.29 135.59 442.93 64 320 64a308.15 308.15 0 0 0-147.32 37.7L45.46 3.37A16 16 0 0 0 23 6.18L3.37 31.45A16 16 0 0 0 6.18 53.9l588.36 454.73a16 16 0 0 0 22.46-2.81l19.64-25.27a16 16 0 0 0-2.82-22.45zm-183.72-142l-39.3-30.38A94.75 94.75 0 0 0 416 256a94.76 94.76 0 0 0-121.31-92.21A47.65 47.65 0 0 1 304 192a46.64 46.64 0 0 1-1.54 10l-73.61-56.89A142.31 142.31 0 0 1 320 112a143.92 143.92 0 0 1 144 144c0 21.63-5.29 41.79-13.9 60.11z"],
"italic" => [320, 512, 10, "M320 48v32a16 16 0 0 1-16 16h-62.76l-80 320H208a16 16 0 0 1 16 16v32a16 16 0 0 1-16 16H16a16 16 0 0 1-16-16v-32a16 16 0 0 1 16-16h62.76l80-320H112a16 16 0 0 1-16-16V48a16 16 0 0 1 16-16h192a16 16 0 0 1 16 16z"],
"lock" => [448, 512, 14, "M400 224h-24v-72C376 68.2 307.8 0 224 0S72 68.2 72 152v72H48c-26.5 0-48 21.5-48 48v192c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V272c0-26.5-21.5-48-48-48zm-104 0H152v-72c0-39.7 32.3-72 72-72s72 32.3 72 72v72z"],
"lock-open" => [576, 512, 18, "M423.5 0C339.5.3 272 69.5 272 153.5V224H48c-26.5 0-48 21.5-48 48v192c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V272c0-26.5-21.5-48-48-48h-48v-71.1c0-39.6 31.7-72.5 71.3-72.9 40-.4 72.7 32.1 72.7 72v80c0 13.3 10.7 24 24 24h32c13.3 0 24-10.7 24-24v-80C576 68 507.5-.3 423.5 0z"],
"minus-circle" => [512, 512, 16, "M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zM124 296c-6.6 0-12-5.4-12-12v-56c0-6.6 5.4-12 12-12h264c6.6 0 12 5.4 12 12v56c0 6.6-5.4 12-12 12H124z"],
"plus" => [448, 512, 14, "M416 208H272V64c0-17.67-14.33-32-32-32h-32c-17.67 0-32 14.33-32 32v144H32c-17.67 0-32 14.33-32 32v32c0 17.67 14.33 32 32 32h144v144c0 17.67 14.33 32 32 32h32c17.67 0 32-14.33 32-32V304h144c17.67 0 32-14.33 32-32v-32c0-17.67-14.33-32-32-32z"],
"plus-circle" => [512, 512, 16, "M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm144 276c0 6.6-5.4 12-12 12h-92v92c0 6.6-5.4 12-12 12h-56c-6.6 0-12-5.4-12-12v-92h-92c-6.6 0-12-5.4-12-12v-56c0-6.6 5.4-12 12-12h92v-92c0-6.6 5.4-12 12-12h56c6.6 0 12 5.4 12 12v92h92c6.6 0 12 5.4 12 12v56z"],
"times" => [352, 512, 11, "M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"]
];
foreach ( $icons as $name => $attr ) {
$svg = '<svg aria-hidden="true" focusable="false" class="svg-inline--fa fa-{name} fa-w-{faw}" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {w} {h}"><path fill="currentColor" d="{d}"></path></svg>';
$search = [ '{name}', '{w}', '{h}', '{faw}', '{d}' ];
$replace = [ $name, $attr[0], $attr[1], $attr[2], $attr[3] ];
$output[ $name ] = str_replace( $search, $replace, $svg );
}
return $output;
}
}

View File

@@ -0,0 +1,183 @@
<?php
class FacetWP_Updater
{
function __construct() {
add_filter( 'plugins_api', [ $this, 'plugins_api' ], 10, 3 );
add_filter( 'pre_set_site_transient_update_plugins', [ $this, 'check_update' ] );
add_action( 'in_plugin_update_message-' . FACETWP_BASENAME, [ $this, 'in_plugin_update_message' ], 10, 2 );
}
/**
* Get info for FacetWP and its add-ons
*/
function get_plugins_to_check() {
$output = [];
$plugins = get_plugins();
if ( is_multisite() ) {
$active_plugins = get_site_option( 'active_sitewide_plugins', [] );
$active_plugins = array_flip( $active_plugins ); // [plugin] => path
}
else {
$active_plugins = get_option( 'active_plugins', [] );
}
foreach ( $active_plugins as $plugin_path ) {
if ( is_multisite() ) {
$plugin_path = str_replace( WP_PLUGIN_DIR, '', $plugin_path );
}
if ( isset( $plugins[ $plugin_path ] ) ) {
$info = $plugins[ $plugin_path ];
$slug = trim( dirname( $plugin_path ), '/' );
// only intercept FacetWP and its add-ons
$is_valid = in_array( $slug, [ 'facetwp', 'user-post-type' ] );
$is_valid = ( 0 === strpos( $slug, 'facetwp-' ) ) ? true : $is_valid;
if ( $is_valid ) {
$output[ $slug ] = [
'name' => $info['Name'],
'version' => $info['Version'],
'description' => $info['Description'],
'plugin_path' => $plugin_path,
];
}
}
}
return $output;
}
/**
* Handle the "View Details" popup
*
* $args->slug = "facetwp-flyout"
* plugin_path = "facetwp-flyout/facetwp-flyout.php"
*/
function plugins_api( $result, $action, $args ) {
if ( 'plugin_information' == $action ) {
$slug = $args->slug;
$to_check = $this->get_plugins_to_check();
$response = get_option( 'facetwp_updater_response', '' );
$response = json_decode( $response, true );
if ( isset( $to_check[ $slug ] ) && isset( $response[ $slug ] ) ) {
$local_data = $to_check[ $slug ];
$remote_data = $response[ $slug ];
return (object) [
'name' => $local_data['name'],
'slug' => $local_data['plugin_path'],
'version' => $remote_data['version'],
'last_updated' => $remote_data['last_updated'],
'download_link' => $remote_data['package'],
'sections' => [
'description' => $local_data['description'],
'changelog' => $remote_data['sections']['changelog']
],
'homepage' => 'https://facetwp.com/',
'rating' => 100,
'num_ratings' => 1
];
}
}
return $result;
}
/**
* Grab (and cache) plugin update data
*/
function check_update( $transient ) {
if ( empty( $transient->checked ) ) {
return $transient;
}
$now = strtotime( 'now' );
$response = get_option( 'facetwp_updater_response', '' );
$ts = (int) get_option( 'facetwp_updater_last_checked' );
$plugins = $this->get_plugins_to_check();
if ( empty( $response ) || $ts + 14400 < $now ) {
$request = wp_remote_post( 'https://api.facetwp.com', [
'body' => [
'action' => 'check_plugins',
'slugs' => array_keys( $plugins ),
'license' => FWP()->helper->get_license_key(),
'host' => FWP()->helper->get_http_host(),
'wp_v' => get_bloginfo( 'version' ),
'fwp_v' => FACETWP_VERSION,
'php_v' => phpversion(),
]
] );
if ( ! is_wp_error( $request ) || 200 == wp_remote_retrieve_response_code( $request ) ) {
$body = json_decode( $request['body'], true );
$activation = json_encode( $body['activation'] );
$response = json_encode( $body['slugs'] );
}
update_option( 'facetwp_activation', $activation );
update_option( 'facetwp_updater_response', $response, 'no' );
update_option( 'facetwp_updater_last_checked', $now, 'no' );
}
if ( ! empty( $response ) ) {
$response = json_decode( $response, true );
foreach ( $response as $slug => $info ) {
if ( isset( $plugins[ $slug ] ) ) {
$plugin_path = $plugins[ $slug ]['plugin_path'];
$response_obj = (object) [
'slug' => $slug,
'plugin' => $plugin_path,
'new_version' => $info['version'],
'package' => $info['package'],
'requires' => $info['requires'],
'tested' => $info['tested']
];
if ( version_compare( $plugins[ $slug ]['version'], $info['version'], '<' ) ) {
$transient->response[ $plugin_path ] = $response_obj;
}
else {
$transient->no_update[ $plugin_path ] = $response_obj;
}
}
}
}
return $transient;
}
/**
* Display a license reminder on the plugin list screen
*/
function in_plugin_update_message( $plugin_data, $response ) {
if ( ! FWP()->helper->is_license_active() ) {
$price_id = (int) FWP()->helper->get_license_meta( 'price_id' );
$license = FWP()->helper->get_license_key();
if ( 0 < $price_id ) {
echo '<br />' . sprintf(
__( 'Please <a href="%s" target="_blank">renew your license</a> for automatic updates.', 'fwp' ),
esc_url( "https://facetwp.com/checkout/?edd_action=add_to_cart&download_id=24&edd_options[price_id]=$price_id&discount=$license" )
);
}
else {
echo '<br />' . __( 'Please activate your FacetWP license for automatic updates.', 'fwp' );
}
}
}
}
new FacetWP_Updater();

View File

@@ -0,0 +1,106 @@
<?php
class FacetWP_Upgrade
{
function __construct() {
$this->version = FACETWP_VERSION;
$this->last_version = get_option( 'facetwp_version' );
if ( version_compare( $this->last_version, $this->version, '<' ) ) {
if ( version_compare( $this->last_version, '0.1.0', '<' ) ) {
require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
$this->clean_install();
}
else {
$this->run_upgrade();
}
update_option( 'facetwp_version', $this->version );
}
}
private function clean_install() {
global $wpdb;
$int = apply_filters( 'facetwp_use_bigint', false ) ? 'BIGINT' : 'INT';
$charset_collate = $wpdb->get_charset_collate();
$sql = "
CREATE TABLE IF NOT EXISTS {$wpdb->prefix}facetwp_index (
id BIGINT unsigned not null auto_increment,
post_id $int unsigned,
facet_name VARCHAR(50),
facet_value VARCHAR(50),
facet_display_value VARCHAR(200),
term_id $int unsigned default '0',
parent_id $int unsigned default '0',
depth INT unsigned default '0',
variation_id $int unsigned default '0',
PRIMARY KEY (id),
INDEX post_id_idx (post_id),
INDEX facet_name_idx (facet_name),
INDEX facet_name_value_idx (facet_name, facet_value)
) $charset_collate";
dbDelta( $sql );
// Add default settings
$settings = file_get_contents( FACETWP_DIR . '/assets/js/src/sample.json' );
add_option( 'facetwp_settings', $settings );
}
private function run_upgrade() {
global $wpdb;
$table = sanitize_key( $wpdb->prefix . 'facetwp_index' );
if ( version_compare( $this->last_version, '3.1.0', '<' ) ) {
$wpdb->query( "ALTER TABLE $table MODIFY facet_name VARCHAR(50)" );
$wpdb->query( "ALTER TABLE $table MODIFY facet_value VARCHAR(50)" );
$wpdb->query( "ALTER TABLE $table MODIFY facet_display_value VARCHAR(200)" );
$wpdb->query( "CREATE INDEX facet_name_value_idx ON $table (facet_name, facet_value)" );
}
if ( version_compare( $this->last_version, '3.3.2', '<' ) ) {
$wpdb->query( "CREATE INDEX post_id_idx ON $table (post_id)" );
$wpdb->query( "DROP INDEX facet_source_idx ON $table" );
$wpdb->query( "ALTER TABLE $table DROP COLUMN facet_source" );
}
if ( version_compare( $this->last_version, '3.3.3', '<' ) ) {
if ( function_exists( 'SWP' ) ) {
$engines = array_keys( SWP()->settings['engines'] );
$settings = get_option( 'facetwp_settings' );
$settings = json_decode( $settings, true );
foreach ( $settings['facets'] as $key => $facet ) {
if ( 'search' == $facet['type'] ) {
if ( in_array( $facet['search_engine'], $engines ) ) {
$settings['facets'][ $key ]['search_engine'] = 'swp_' . $facet['search_engine'];
}
}
}
update_option( 'facetwp_settings', json_encode( $settings ) );
}
}
if ( version_compare( $this->last_version, '3.5.3', '<' ) ) {
update_option( 'facetwp_updater_response', '', 'no' );
update_option( 'facetwp_updater_last_checked', '', 'no' );
}
if ( version_compare( $this->last_version, '3.5.4', '<' ) ) {
$settings = get_option( 'facetwp_settings' );
$settings = json_decode( $settings, true );
if ( ! isset( $settings['settings']['prefix'] ) ) {
$settings['settings']['prefix'] = 'fwp_';
update_option( 'facetwp_settings', json_encode( $settings ) );
}
}
}
}

View File

@@ -0,0 +1,163 @@
<?php
class FacetWP_Facet_Autocomplete extends FacetWP_Facet
{
public $is_buffering = false;
function __construct() {
$this->label = __( 'Autocomplete', 'fwp' );
$this->fields = [ 'placeholder' ];
// ajax
add_action( 'facetwp_autocomplete_load', [ $this, 'ajax_load' ] );
// css-based template
add_action( 'facetwp_init', [ $this, 'maybe_buffer_output' ] );
add_action( 'facetwp_found_main_query', [ $this, 'template_handler' ] );
// result limit
$this->limit = (int) apply_filters( 'facetwp_facet_autocomplete_limit', 10 );
}
/**
* For page templates with a custom WP_Query, we need to prevent the
* page header from being output with the autocomplete JSON
*/
function maybe_buffer_output() {
if ( isset( $_POST['action'] ) && 'facetwp_autocomplete_load' == $_POST['action'] ) {
$this->is_buffering = true;
ob_start();
}
}
/**
* For CSS-based templates, the "facetwp_autocomplete_load" action isn't fired
* so we need to manually check the action
*/
function template_handler() {
if ( isset( $_POST['action'] ) && 'facetwp_autocomplete_load' == $_POST['action'] ) {
if ( $this->is_buffering ) {
while ( ob_get_level() ) {
ob_end_clean();
}
}
$this->ajax_load();
}
}
/**
* Generate the facet HTML
*/
function render( $params ) {
$output = '';
$facet = $params['facet'];
$value = (array) $params['selected_values'];
$value = empty( $value ) ? '' : stripslashes( $value[0] );
$placeholder = empty( $facet['placeholder'] ) ? __( 'Start typing', 'fwp-front' ) : $facet['placeholder'];
$placeholder = facetwp_i18n( $placeholder );
$output .= '<input type="text" class="facetwp-autocomplete" value="' . esc_attr( $value ) . '" placeholder="' . esc_attr( $placeholder ) . '" autocomplete="off" />';
$output .= '<input type="button" class="facetwp-autocomplete-update" value="' . __( 'Go', 'fwp-front' ) . '" />';
return $output;
}
/**
* Filter the query based on selected values
*/
function filter_posts( $params ) {
global $wpdb;
$facet = $params['facet'];
$selected_values = $params['selected_values'];
$selected_values = is_array( $selected_values ) ? $selected_values[0] : $selected_values;
$selected_values = stripslashes( $selected_values );
if ( empty( $selected_values ) ) {
return 'continue';
}
$sql = "
SELECT DISTINCT post_id FROM {$wpdb->prefix}facetwp_index
WHERE facet_name = %s AND facet_display_value LIKE %s";
$sql = $wpdb->prepare( $sql, $facet['name'], '%' . $selected_values . '%' );
return facetwp_sql( $sql, $facet );
}
/**
* Output any front-end scripts
*/
function front_scripts() {
FWP()->display->json['no_results'] = __( 'No results', 'fwp-front' );
FWP()->display->assets['fComplete.js'] = FACETWP_URL . '/assets/vendor/fComplete/fComplete.js';
FWP()->display->assets['fComplete.css'] = FACETWP_URL . '/assets/vendor/fComplete/fComplete.css';
}
/**
* Load facet values via AJAX
*/
function ajax_load() {
global $wpdb;
// optimizations
$_POST['data']['soft_refresh'] = 1;
$_POST['data']['extras'] = [];
$query = stripslashes( $_POST['query'] );
$query = FWP()->helper->sanitize( $wpdb->esc_like( $query ) );
$facet_name = FWP()->helper->sanitize( $_POST['facet_name'] );
$output = [];
// simulate a refresh
FWP()->facet->render(
FWP()->request->process_post_data()
);
// then grab the matching post IDs
$where_clause = $this->get_where_clause( [ 'name' => $facet_name ] );
if ( ! empty( $query ) && ! empty( $facet_name ) ) {
$sql = "
SELECT DISTINCT facet_display_value
FROM {$wpdb->prefix}facetwp_index
WHERE
facet_name = '$facet_name' AND
facet_display_value LIKE '%$query%'
$where_clause
ORDER BY facet_display_value ASC
LIMIT $this->limit";
$results = $wpdb->get_results( $sql );
foreach ( $results as $result ) {
$output[] = [
'value' => $result->facet_display_value,
'label' => $result->facet_display_value,
];
}
}
wp_send_json( $output );
}
/**
* (Front-end) Attach settings to the AJAX response
*/
function settings_js( $params ) {
return [
'loadingText' => __( 'Loading', 'fwp-front' ) . '...',
'minCharsText' => __( 'Enter {n} or more characters', 'fwp-front' ),
'noResultsText' => __( 'No results', 'fwp-front' ),
'maxResults' => $this->limit
];
}
}

View File

@@ -0,0 +1,106 @@
<?php
class FacetWP_Facet
{
/**
* Grab the orderby, as needed by several facet types
* @since 3.0.4
*/
function get_orderby( $facet ) {
$key = $facet['orderby'];
// Count (default)
$orderby = 'counter DESC, f.facet_display_value ASC';
// Display value
if ( 'display_value' == $key ) {
$orderby = 'f.facet_display_value ASC';
}
// Raw value
elseif ( 'raw_value' == $key ) {
$orderby = 'f.facet_value ASC';
}
// Term order
elseif ( 'term_order' == $key && 'tax' == substr( $facet['source'], 0, 3 ) ) {
$term_ids = get_terms( [
'taxonomy' => str_replace( 'tax/', '', $facet['source'] ),
'term_order' => true, // Custom flag
'fields' => 'ids',
] );
if ( ! empty( $term_ids ) && ! is_wp_error( $term_ids ) ) {
$term_ids = implode( ',', $term_ids );
$orderby = "FIELD(f.term_id, $term_ids)";
}
}
// Sort by depth
if ( FWP()->helper->facet_is( $facet, 'hierarchical', 'yes' ) ) {
$orderby = "f.depth, $orderby";
}
return $orderby;
}
/**
* Grab the limit, and support -1
* @since 3.5.4
*/
function get_limit( $facet, $default = 10 ) {
$count = $facet['count'];
if ( '-1' == $count ) {
return 1000;
}
elseif ( ctype_digit( $count ) ) {
return $count;
}
return $default;
}
/**
* Adjust the $where_clause for facets in "OR" mode
*
* FWP()->or_values contains EVERY facet and their matching post IDs
* FWP()->unfiltered_post_ids contains original post IDs
*
* @since 3.2.0
*/
function get_where_clause( $facet ) {
// Ignore the current facet's selections
if ( isset( FWP()->or_values ) && ( 1 < count( FWP()->or_values ) || ! isset( FWP()->or_values[ $facet['name'] ] ) ) ) {
$post_ids = [];
$or_values = FWP()->or_values; // Preserve original
unset( $or_values[ $facet['name'] ] );
$counter = 0;
foreach ( $or_values as $name => $vals ) {
$post_ids = ( 0 == $counter ) ? $vals : array_intersect( $post_ids, $vals );
$counter++;
}
$post_ids = array_intersect( $post_ids, FWP()->unfiltered_post_ids );
}
else {
$post_ids = FWP()->unfiltered_post_ids;
}
$post_ids = empty( $post_ids ) ? [ 0 ] : $post_ids;
return ' AND post_id IN (' . implode( ',', $post_ids ) . ')';
}
/**
* Render some commonly used admin settings
* @since 3.5.6
* @deprecated 3.9
*/
function render_setting( $name ) {
echo FWP()->settings->get_facet_field_html( $name );
}
}

View File

@@ -0,0 +1,258 @@
<?php
class FacetWP_Facet_Checkboxes extends FacetWP_Facet
{
function __construct() {
$this->label = __( 'Checkboxes', 'fwp' );
$this->fields = [ 'parent_term', 'modifiers', 'hierarchical', 'show_expanded',
'ghosts', 'operator', 'orderby', 'count', 'soft_limit' ];
}
/**
* Load the available choices
*/
function load_values( $params ) {
global $wpdb;
$facet = $params['facet'];
$from_clause = $wpdb->prefix . 'facetwp_index f';
$where_clause = $params['where_clause'];
// Orderby
$orderby = $this->get_orderby( $facet );
// Limit
$limit = $this->get_limit( $facet );
// Use "OR" mode when necessary
$is_single = FWP()->helper->facet_is( $facet, 'multiple', 'no' );
$using_or = FWP()->helper->facet_is( $facet, 'operator', 'or' );
// Facet in "OR" mode
if ( $is_single || $using_or ) {
$where_clause = $this->get_where_clause( $facet );
}
$orderby = apply_filters( 'facetwp_facet_orderby', $orderby, $facet );
$from_clause = apply_filters( 'facetwp_facet_from', $from_clause, $facet );
$where_clause = apply_filters( 'facetwp_facet_where', $where_clause, $facet );
$sql = "
SELECT f.facet_value, f.facet_display_value, f.term_id, f.parent_id, f.depth, COUNT(DISTINCT f.post_id) AS counter
FROM $from_clause
WHERE f.facet_name = '{$facet['name']}' $where_clause
GROUP BY f.facet_value
ORDER BY $orderby
LIMIT $limit";
$output = $wpdb->get_results( $sql, ARRAY_A );
// Show "ghost" facet choices
// For performance gains, only run if facets are in use
$show_ghosts = FWP()->helper->facet_is( $facet, 'ghosts', 'yes' );
if ( $show_ghosts && FWP()->is_filtered ) {
$raw_post_ids = implode( ',', FWP()->unfiltered_post_ids );
$sql = "
SELECT f.facet_value, f.facet_display_value, f.term_id, f.parent_id, f.depth, 0 AS counter
FROM $from_clause
WHERE f.facet_name = '{$facet['name']}' AND post_id IN ($raw_post_ids)
GROUP BY f.facet_value
ORDER BY $orderby
LIMIT $limit";
$ghost_output = $wpdb->get_results( $sql, ARRAY_A );
$tmp = [];
$preserve_ghosts = FWP()->helper->facet_is( $facet, 'preserve_ghosts', 'yes' );
$orderby_count = FWP()->helper->facet_is( $facet, 'orderby', 'count' );
// Keep the facet placement intact
if ( $preserve_ghosts && ! $orderby_count ) {
foreach ( $ghost_output as $row ) {
$tmp[ $row['facet_value'] . ' ' ] = $row;
}
foreach ( $output as $row ) {
$tmp[ $row['facet_value'] . ' ' ] = $row;
}
$output = $tmp;
}
else {
// Make the array key equal to the facet_value (for easy lookup)
foreach ( $output as $row ) {
$tmp[ $row['facet_value'] . ' ' ] = $row; // Force a string array key
}
$output = $tmp;
foreach ( $ghost_output as $row ) {
$facet_value = $row['facet_value'];
if ( ! isset( $output[ "$facet_value " ] ) ) {
$output[ "$facet_value " ] = $row;
}
}
}
$output = array_splice( $output, 0, $limit );
$output = array_values( $output );
}
return $output;
}
/**
* Generate the facet HTML
*/
function render( $params ) {
$facet = $params['facet'];
if ( FWP()->helper->facet_is( $facet, 'hierarchical', 'yes' ) ) {
return $this->render_hierarchy( $params );
}
$output = '';
$values = (array) $params['values'];
$soft_limit = empty( $facet['soft_limit'] ) ? 0 : (int) $facet['soft_limit'];
$key = 0;
foreach ( $values as $key => $row ) {
if ( 0 < $soft_limit && $key == $soft_limit ) {
$output .= '<div class="facetwp-overflow facetwp-hidden">';
}
$output .= $this->render_choice( $row, $params );
}
if ( 0 < $soft_limit && $soft_limit <= $key ) {
$output .= '</div>';
$output .= '<a class="facetwp-toggle">' . __( 'See {num} more', 'fwp-front' ) . '</a>';
$output .= '<a class="facetwp-toggle facetwp-hidden">' . __( 'See less', 'fwp-front' ) . '</a>';
}
return $output;
}
/**
* Generate the facet HTML (hierarchical taxonomies)
*/
function render_hierarchy( $params ) {
$output = '';
$facet = $params['facet'];
$values = FWP()->helper->sort_taxonomy_values( $params['values'], $facet['orderby'] );
$init_depth = -1;
$last_depth = -1;
foreach ( $values as $row ) {
$depth = (int) $row['depth'];
if ( -1 == $last_depth ) {
$init_depth = $depth;
}
elseif ( $depth > $last_depth ) {
$output .= '<div class="facetwp-depth">';
}
elseif ( $depth < $last_depth ) {
for ( $i = $last_depth; $i > $depth; $i-- ) {
$output .= '</div>';
}
}
$output .= $this->render_choice( $row, $params );
$last_depth = $depth;
}
for ( $i = $last_depth; $i > $init_depth; $i-- ) {
$output .= '</div>';
}
return $output;
}
/**
* Render a single facet choice
*/
function render_choice( $row, $params ) {
$label = esc_html( $row['facet_display_value'] );
$output = '';
$selected_values = (array) $params['selected_values'];
$selected = in_array( $row['facet_value'], $selected_values ) ? ' checked' : '';
$selected .= ( '' != $row['counter'] && 0 == $row['counter'] && '' == $selected ) ? ' disabled' : '';
$output .= '<div class="facetwp-checkbox' . $selected . '" data-value="' . esc_attr( $row['facet_value'] ) . '">';
$output .= '<span class="facetwp-display-value">';
$output .= apply_filters( 'facetwp_facet_display_value', $label, [
'selected' => ( '' !== $selected ),
'facet' => $params['facet'],
'row' => $row
]);
$output .= '</span>';
$output .= '<span class="facetwp-counter">(' . $row['counter'] . ')</span>';
$output .= '</div>';
return $output;
}
/**
* Filter the query based on selected values
*/
function filter_posts( $params ) {
global $wpdb;
$output = [];
$facet = $params['facet'];
$selected_values = $params['selected_values'];
$sql = $wpdb->prepare( "SELECT DISTINCT post_id
FROM {$wpdb->prefix}facetwp_index
WHERE facet_name = %s",
$facet['name']
);
// Match ALL values
if ( 'and' == $facet['operator'] ) {
foreach ( $selected_values as $key => $value ) {
$results = facetwp_sql( $sql . " AND facet_value IN ('$value')", $facet );
$output = ( $key > 0 ) ? array_intersect( $output, $results ) : $results;
if ( empty( $output ) ) {
break;
}
}
}
// Match ANY value
else {
$selected_values = implode( "','", $selected_values );
$output = facetwp_sql( $sql . " AND facet_value IN ('$selected_values')", $facet );
}
return $output;
}
/**
* Output any front-end scripts
*/
function front_scripts() {
FWP()->display->json['expand'] = '[+]';
FWP()->display->json['collapse'] = '[-]';
}
/**
* (Front-end) Attach settings to the AJAX response
*/
function settings_js( $params ) {
$expand = empty( $params['facet']['show_expanded'] ) ? 'no' : $params['facet']['show_expanded'];
return [ 'show_expanded' => $expand ];
}
}

View File

@@ -0,0 +1,292 @@
<?php
class FacetWP_Facet_Date_Range extends FacetWP_Facet
{
function __construct() {
$this->label = __( 'Date Range', 'fwp' );
$this->fields = [ 'source_other', 'compare_type', 'date_fields', 'date_format' ];
}
/**
* Generate the facet HTML
*/
function render( $params ) {
$output = '';
$value = $params['selected_values'];
$value = empty( $value ) ? [ '', '' ] : $value;
$fields = empty( $params['facet']['fields'] ) ? 'both' : $params['facet']['fields'];
$text_date = facetwp_i18n( __( 'Date', 'fwp-front' ) );
$text_start_date = facetwp_i18n( __( 'Start Date', 'fwp-front' ) );
$text_end_date = facetwp_i18n( __( 'End Date', 'fwp-front' ) );
if ( 'exact' == $fields ) {
$output .= '<input type="text" class="facetwp-date facetwp-date-min" value="' . esc_attr( $value[0] ) . '" placeholder="' . esc_attr( $text_date ) . '" />';
}
if ( 'both' == $fields || 'start_date' == $fields ) {
$output .= '<input type="text" class="facetwp-date facetwp-date-min" value="' . esc_attr( $value[0] ) . '" placeholder="' . esc_attr( $text_start_date ) . '" />';
}
if ( 'both' == $fields || 'end_date' == $fields ) {
$output .= '<input type="text" class="facetwp-date facetwp-date-max" value="' . esc_attr( $value[1] ) . '" placeholder="' . esc_attr( $text_end_date ) . '" />';
}
return $output;
}
/**
* Filter the query based on selected values
*/
function filter_posts( $params ) {
global $wpdb;
$facet = $params['facet'];
$values = $params['selected_values'];
$where = '';
$min = empty( $values[0] ) ? false : $values[0];
$max = empty( $values[1] ) ? false : $values[1];
$fields = $facet['fields'] ?? 'both';
$compare_type = empty( $facet['compare_type'] ) ? 'basic' : $facet['compare_type'];
$is_dual = ! empty( $facet['source_other'] );
if ( $is_dual && 'basic' != $compare_type ) {
if ( 'exact' == $fields ) {
$max = $min;
}
$min = ( false !== $min ) ? $min : '0000-00-00';
$max = ( false !== $max ) ? $max : '3000-12-31';
/**
* Enclose compare
* The post's range must surround the user-defined range
*/
if ( 'enclose' == $compare_type ) {
$where .= " AND LEFT(facet_value, 10) <= '$min'";
$where .= " AND LEFT(facet_display_value, 10) >= '$max'";
}
/**
* Intersect compare
* @link http://stackoverflow.com/a/325964
*/
if ( 'intersect' == $compare_type ) {
$where .= " AND LEFT(facet_value, 10) <= '$max'";
$where .= " AND LEFT(facet_display_value, 10) >= '$min'";
}
}
/**
* Basic compare
* The user-defined range must surround the post's range
*/
else {
if ( 'exact' == $fields ) {
$max = $min;
}
if ( false !== $min ) {
$where .= " AND LEFT(facet_value, 10) >= '$min'";
}
if ( false !== $max ) {
$where .= " AND LEFT(facet_display_value, 10) <= '$max'";
}
}
$sql = "
SELECT DISTINCT post_id FROM {$wpdb->prefix}facetwp_index
WHERE facet_name = '{$facet['name']}' $where";
return facetwp_sql( $sql, $facet );
}
/**
* Output any front-end scripts
*/
function front_scripts() {
FWP()->display->assets['fDate.css'] = FACETWP_URL . '/assets/vendor/fDate/fDate.css';
FWP()->display->assets['fDate.js'] = FACETWP_URL . '/assets/vendor/fDate/fDate.min.js';
}
function register_fields() {
return [
'date_fields' => [
'type' => 'alias',
'items' => [
'fields' => [
'type' => 'select',
'label' => __( 'Fields to show', 'fwp' ),
'choices' => [
'both' => __( 'Start + End Dates', 'fwp' ),
'exact' => __( 'Exact Date', 'fwp' ),
'start_date' => __( 'Start Date', 'fwp' ),
'end_date' => __( 'End Date', 'fwp' )
]
]
]
],
'date_format' => [
'type' => 'alias',
'items' => [
'format' => [
'label' => __( 'Display format', 'fwp' ),
'notes' => 'See available <a href="https://facetwp.com/help-center/facets/facet-types/date-range/#tokens" target="_blank">formatting tokens</a>',
'placeholder' => 'Y-m-d'
]
]
]
];
}
/**
* (Front-end) Attach settings to the AJAX response
*/
function settings_js( $params ) {
global $wpdb;
$facet = $params['facet'];
$selected_values = $params['selected_values'];
$fields = empty( $facet['fields'] ) ? 'both' : $facet['fields'];
$format = empty( $facet['format'] ) ? '' : $facet['format'];
// Use "OR" mode by excluding the facet's own selection
$where_clause = $this->get_where_clause( $facet );
$sql = "
SELECT MIN(facet_value) AS `minDate`, MAX(facet_display_value) AS `maxDate` FROM {$wpdb->prefix}facetwp_index
WHERE facet_name = '{$facet['name']}' AND facet_display_value != '' $where_clause";
$row = $wpdb->get_row( $sql );
$min = substr( $row->minDate, 0, 10 );
$max = substr( $row->maxDate, 0, 10 );
if ( 'both' == $fields ) {
$min_upper = ! empty( $selected_values[1] ) ? $selected_values[1] : $max;
$max_lower = ! empty( $selected_values[0] ) ? $selected_values[0] : $min;
$range = [
'min' => [
'minDate' => $min,
'maxDate' => $min_upper
],
'max' => [
'minDate' => $max_lower,
'maxDate' => $max
]
];
}
else {
$range = [
'minDate' => $min,
'maxDate' => $max
];
}
return [
'locale' => $this->get_i18n_labels(),
'format' => $format,
'fields' => $fields,
'range' => $range
];
}
function get_i18n_labels() {
$locale = get_locale();
$locale = empty( $locale ) ? 'en' : substr( $locale, 0, 2 );
$locales = [
'ca' => [
'weekdays_short' => ['Dg', 'Dl', 'Dt', 'Dc', 'Dj', 'Dv', 'Ds'],
'months_short' => ['Gen', 'Febr', 'Març', 'Abr', 'Maig', 'Juny', 'Jul', 'Ag', 'Set', 'Oct', 'Nov', 'Des'],
'months' => ['Gener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juliol', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Desembre'],
'firstDayOfWeek' => 1
],
'da' => [
'weekdays_short' => ['søn', 'man', 'tir', 'ons', 'tors', 'fre', 'lør'],
'months_short' => ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'],
'months' => ['januar', 'februar', 'marts', 'april', 'maj', 'juni', 'juli', 'august', 'september', 'oktober', 'november', 'december'],
'firstDayOfWeek' => 1
],
'de' => [
'weekdays_short' => ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
'months_short' => ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'],
'months' => ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
'firstDayOfWeek' => 1
],
'es' => [
'weekdays_short' => ['Dom', 'Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb'],
'months_short' => ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic'],
'months' => ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'],
'firstDayOfWeek' => 1
],
'fr' => [
'weekdays_short' => ['dim', 'lun', 'mar', 'mer', 'jeu', 'ven', 'sam'],
'months_short' => ['janv', 'févr', 'mars', 'avr', 'mai', 'juin', 'juil', 'août', 'sept', 'oct', 'nov', 'déc'],
'months' => ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre'],
'firstDayOfWeek' => 1
],
'it' => [
'weekdays_short' => ['Dom', 'Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab'],
'months_short' => ['Gen', 'Feb', 'Mar', 'Apr', 'Mag', 'Giu', 'Lug', 'Ago', 'Set', 'Ott', 'Nov', 'Dic'],
'months' => ['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'],
'firstDayOfWeek' => 1
],
'nb' => [
'weekdays_short' => ['Søn', 'Man', 'Tir', 'Ons', 'Tor', 'Fre', 'Lør'],
'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'],
'months' => ['Januar', 'Februar', 'Mars', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Desember'],
'firstDayOfWeek' => 1
],
'nl' => [
'weekdays_short' => ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'],
'months_short' => ['jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sept', 'okt', 'nov', 'dec'],
'months' => ['januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december'],
'firstDayOfWeek' => 1
],
'pl' => [
'weekdays_short' => ['Nd', 'Pn', 'Wt', 'Śr', 'Cz', 'Pt', 'So'],
'months_short' => ['Sty', 'Lut', 'Mar', 'Kwi', 'Maj', 'Cze', 'Lip', 'Sie', 'Wrz', 'Paź', 'Lis', 'Gru'],
'months' => ['Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec', 'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień'],
'firstDayOfWeek' => 1
],
'pt' => [
'weekdays_short' => ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'],
'months_short' => ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'],
'months' => ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'],
'firstDayOfWeek' => 0
],
'ro' => [
'weekdays_short' => ['Dum', 'Lun', 'Mar', 'Mie', 'Joi', 'Vin', 'Sâm'],
'months_short' => ['Ian', 'Feb', 'Mar', 'Apr', 'Mai', 'Iun', 'Iul', 'Aug', 'Sep', 'Oct', 'Noi', 'Dec'],
'months' => ['Ianuarie', 'Februarie', 'Martie', 'Aprilie', 'Mai', 'Iunie', 'Iulie', 'August', 'Septembrie', 'Octombrie', 'Noiembrie', 'Decembrie'],
'firstDayOfWeek' => 1
],
'ru' => [
'weekdays_short' => ['Вс', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'],
'months_short' => ['Янв', 'Фев', 'Март', 'Апр', 'Май', 'Июнь', 'Июль', 'Авг', 'Сен', 'Окт', 'Ноя', 'Дек'],
'months' => ['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'],
'firstDayOfWeek' => 1
],
'sv' => [
'weekdays_short' => ['Sön', 'Mån', 'Tis', 'Ons', 'Tor', 'Fre', 'Lör'],
'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec'],
'months' => ['Januari', 'Februari', 'Mars', 'April', 'Maj', 'Juni', 'Juli', 'Augusti', 'September', 'Oktober', 'November', 'December'],
'firstDayOfWeek' => 1
]
];
if ( isset( $locales[ $locale ] ) ) {
$locales[ $locale ]['clearText'] = __( 'Clear', 'fwp-front' );
return $locales[ $locale ];
}
return '';
}
}

View File

@@ -0,0 +1,73 @@
<?php
class FacetWP_Facet_Dropdown extends FacetWP_Facet
{
function __construct() {
$this->label = __( 'Dropdown', 'fwp' );
$this->fields = [ 'label_any', 'parent_term', 'modifiers', 'hierarchical', 'orderby', 'count' ];
}
/**
* Load the available choices
*/
function load_values( $params ) {
return FWP()->helper->facet_types['checkboxes']->load_values( $params );
}
/**
* Generate the facet HTML
*/
function render( $params ) {
$output = '';
$facet = $params['facet'];
$values = (array) $params['values'];
$selected_values = (array) $params['selected_values'];
$is_hierarchical = FWP()->helper->facet_is( $facet, 'hierarchical', 'yes' );
if ( $is_hierarchical ) {
$values = FWP()->helper->sort_taxonomy_values( $params['values'], $facet['orderby'] );
}
$label_any = empty( $facet['label_any'] ) ? __( 'Any', 'fwp-front' ) : $facet['label_any'];
$label_any = facetwp_i18n( $label_any );
$output .= '<select class="facetwp-dropdown">';
$output .= '<option value="">' . esc_attr( $label_any ) . '</option>';
foreach ( $values as $row ) {
$selected = in_array( $row['facet_value'], $selected_values ) ? ' selected' : '';
$indent = $is_hierarchical ? str_repeat( '&nbsp;&nbsp;', (int) $row['depth'] ) : '';
// Determine whether to show counts
$label = esc_attr( $row['facet_display_value'] );
$label = apply_filters( 'facetwp_facet_display_value', $label, [
'selected' => ( '' !== $selected ),
'facet' => $facet,
'row' => $row
]);
$show_counts = apply_filters( 'facetwp_facet_dropdown_show_counts', true, [ 'facet' => $facet ] );
if ( $show_counts ) {
$label .= ' (' . $row['counter'] . ')';
}
$output .= '<option value="' . esc_attr( $row['facet_value'] ) . '"' . $selected . '>' . $indent . $label . '</option>';
}
$output .= '</select>';
return $output;
}
/**
* Filter the query based on selected values
*/
function filter_posts( $params ) {
return FWP()->helper->facet_types['checkboxes']->filter_posts( $params );
}
}

View File

@@ -0,0 +1,100 @@
<?php
class FacetWP_Facet_fSelect extends FacetWP_Facet
{
function __construct() {
$this->label = __( 'fSelect', 'fwp' );
$this->fields = [ 'label_any', 'parent_term', 'modifiers', 'hierarchical', 'multiple',
'ghosts', 'operator', 'orderby', 'count' ];
}
/**
* Load the available choices
*/
function load_values( $params ) {
return FWP()->helper->facet_types['checkboxes']->load_values( $params );
}
/**
* Generate the facet HTML
*/
function render( $params ) {
$output = '';
$facet = $params['facet'];
$values = (array) $params['values'];
$selected_values = (array) $params['selected_values'];
$is_hierarchical = FWP()->helper->facet_is( $facet, 'hierarchical', 'yes' );
if ( $is_hierarchical ) {
$values = FWP()->helper->sort_taxonomy_values( $params['values'], $facet['orderby'] );
}
$multiple = FWP()->helper->facet_is( $facet, 'multiple', 'yes' ) ? ' multiple="multiple"' : '';
$label_any = empty( $facet['label_any'] ) ? __( 'Any', 'fwp-front' ) : $facet['label_any'];
$label_any = facetwp_i18n( $label_any );
$output .= '<select class="facetwp-dropdown"' . $multiple . '>';
$output .= '<option value="">' . esc_html( $label_any ) . '</option>';
foreach ( $values as $row ) {
$selected = in_array( $row['facet_value'], $selected_values, true ) ? ' selected' : '';
$disabled = ( 0 == $row['counter'] && '' == $selected ) ? ' disabled' : '';
$label = esc_html( $row['facet_display_value'] );
$label = apply_filters( 'facetwp_facet_display_value', $label, [
'selected' => ( '' !== $selected ),
'facet' => $facet,
'row' => $row
]);
$show_counts = apply_filters( 'facetwp_facet_dropdown_show_counts', true, [ 'facet' => $facet ] );
$counter = $show_counts ? $row['counter'] : '';
$depth = $is_hierarchical ? $row['depth'] : 0;
$output .= '<option value="' . esc_attr( $row['facet_value'] ) . '" data-counter="' . $counter . '" class="d' . $depth . '"' . $selected . $disabled . '>' . $label . '</option>';
}
$output .= '</select>';
return $output;
}
/**
* Filter the query based on selected values
*/
function filter_posts( $params ) {
return FWP()->helper->facet_types['checkboxes']->filter_posts( $params );
}
/**
* (Front-end) Attach settings to the AJAX response
*/
function settings_js( $params ) {
$facet = $params['facet'];
$label_any = empty( $facet['label_any'] ) ? __( 'Any', 'fwp-front' ) : $facet['label_any'];
$label_any = facetwp_i18n( $label_any );
return [
'placeholder' => $label_any,
'overflowText' => __( '{n} selected', 'fwp-front' ),
'searchText' => __( 'Search', 'fwp-front' ),
'noResultsText' => __( 'No results found', 'fwp-front' ),
'operator' => $facet['operator']
];
}
/**
* Output any front-end scripts
*/
function front_scripts() {
FWP()->display->assets['fSelect.css'] = FACETWP_URL . '/assets/vendor/fSelect/fSelect.css';
FWP()->display->assets['fSelect.js'] = FACETWP_URL . '/assets/vendor/fSelect/fSelect.js';
}
}

View File

@@ -0,0 +1,179 @@
<?php
class FacetWP_Facet_Hierarchy extends FacetWP_Facet
{
function __construct() {
$this->label = __( 'Hierarchy', 'fwp' );
$this->fields = [ 'label_any', 'modifiers', 'orderby', 'count' ];
}
/**
* Load the available choices
*/
function load_values( $params ) {
global $wpdb;
$facet = $params['facet'];
$from_clause = $wpdb->prefix . 'facetwp_index f';
$where_clause = $params['where_clause'];
$selected_values = (array) $params['selected_values'];
$facet_parent_id = 0;
$output = [];
$label_any = empty( $facet['label_any'] ) ? __( 'Any', 'fwp-front' ) : $facet['label_any'];
$label_any = facetwp_i18n( $label_any );
// Orderby
$orderby = $this->get_orderby( $facet );
// Determine the parent_id and depth
if ( ! empty( $selected_values[0] ) ) {
// Get term ID from slug
$sql = "
SELECT t.term_id
FROM {$wpdb->terms} t
INNER JOIN {$wpdb->term_taxonomy} tt ON tt.term_id = t.term_id AND tt.taxonomy = %s
WHERE t.slug = %s
LIMIT 1";
$value = $selected_values[0];
$taxonomy = str_replace( 'tax/', '', $facet['source'] );
$facet_parent_id = (int) $wpdb->get_var( $wpdb->prepare( $sql, $taxonomy, $value ) );
// Invalid term
if ( $facet_parent_id < 1 ) {
return [];
}
// Create term lookup array
$depths = FWP()->helper->get_term_depths( $taxonomy );
$max_depth = (int) $depths[ $facet_parent_id ]['depth'];
$last_parent_id = $facet_parent_id;
// Loop backwards
for ( $i = 0; $i <= $max_depth; $i++ ) {
$output[] = [
'facet_value' => $depths[ $last_parent_id ]['slug'],
'facet_display_value' => $depths[ $last_parent_id ]['name'],
'depth' => $depths[ $last_parent_id ]['depth'] + 1,
'counter' => 1, // FWP.settings.num_choices
];
$last_parent_id = (int) $depths[ $last_parent_id ]['parent_id'];
}
$output[] = [
'facet_value' => '',
'facet_display_value' => $label_any,
'depth' => 0,
'counter' => 1,
];
// Reverse it
$output = array_reverse( $output );
}
// Update the WHERE clause
$where_clause .= " AND parent_id = '$facet_parent_id'";
$orderby = apply_filters( 'facetwp_facet_orderby', $orderby, $facet );
$from_clause = apply_filters( 'facetwp_facet_from', $from_clause, $facet );
$where_clause = apply_filters( 'facetwp_facet_where', $where_clause, $facet );
$sql = "
SELECT f.facet_value, f.facet_display_value, COUNT(DISTINCT f.post_id) AS counter
FROM $from_clause
WHERE f.facet_name = '{$facet['name']}' $where_clause
GROUP BY f.facet_value
ORDER BY $orderby";
$results = $wpdb->get_results( $sql, ARRAY_A );
$new_depth = empty( $output ) ? 0 : $output[ count( $output ) - 1 ]['depth'] + 1;
foreach ( $results as $result ) {
$result['depth'] = $new_depth;
$result['is_choice'] = true;
$output[] = $result;
}
return $output;
}
/**
* Generate the facet HTML
*/
function render( $params ) {
$facet = $params['facet'];
$values = (array) $params['values'];
$selected_values = (array) $params['selected_values'];
$output = '';
$num_visible = $this->get_limit( $facet );
$num = 0;
if ( ! empty( $values ) ) {
foreach ( $values as $row ) {
$last_depth = $last_depth ?? $row['depth'];
$selected = ( ! empty( $selected_values ) && $row['facet_value'] == $selected_values[0] );
$label = esc_html( $row['facet_display_value'] );
$label = apply_filters( 'facetwp_facet_display_value', $label, [
'selected' => $selected,
'facet' => $facet,
'row' => $row
]);
if ( $row['depth'] > $last_depth ) {
$output .= '<div class="facetwp-depth">';
}
if ( $num == $num_visible ) {
$output .= '<div class="facetwp-overflow facetwp-hidden">';
}
if ( ! $selected ) {
if ( isset( $row['is_choice'] ) ) {
$label .= ' <span class="facetwp-counter">(' . $row['counter'] . ')</span>';
}
else {
$arrow = apply_filters( 'facetwp_facet_hierarchy_arrow', '&#8249; ' );
$label = $arrow . $label;
}
}
$output .= '<div class="facetwp-link' . ( $selected ? ' checked' : '' ) . '" data-value="' . esc_attr( $row['facet_value'] ) . '">' . $label . '</div>';
if ( isset( $row['is_choice'] ) ) {
$num++;
}
$last_depth = $row['depth'];
}
if ( $num_visible < $num ) {
$output .= '</div>';
$output .= '<a class="facetwp-toggle">' . __( 'See more', 'fwp-front' ) . '</a>';
$output .= '<a class="facetwp-toggle facetwp-hidden">' . __( 'See less', 'fwp-front' ) . '</a>';
}
for ( $i = 0; $i <= $last_depth; $i++ ) {
$output .= '</div>';
}
}
return $output;
}
/**
* Filter the query based on selected values
*/
function filter_posts( $params ) {
return FWP()->helper->facet_types['checkboxes']->filter_posts( $params );
}
}

View File

@@ -0,0 +1,137 @@
<?php
class FacetWP_Facet_Number_Range extends FacetWP_Facet
{
function __construct() {
$this->label = __( 'Number Range', 'fwp' );
$this->fields = [ 'source_other', 'compare_type', 'number_fields' ];
}
/**
* Generate the facet HTML
*/
function render( $params ) {
$output = '';
$value = $params['selected_values'];
$value = empty( $value ) ? [ '', '', ] : $value;
$fields = empty( $params['facet']['fields'] ) ? 'both' : $params['facet']['fields'];
if ( 'exact' == $fields ) {
$output .= '<input type="text" class="facetwp-number facetwp-number-min" value="' . esc_attr( $value[0] ) . '" placeholder="' . __( 'Number', 'fwp-front' ) . '" />';
}
if ( 'both' == $fields || 'min' == $fields ) {
$output .= '<input type="text" class="facetwp-number facetwp-number-min" value="' . esc_attr( $value[0] ) . '" placeholder="' . __( 'Min', 'fwp-front' ) . '" />';
}
if ( 'both' == $fields || 'max' == $fields ) {
$output .= '<input type="text" class="facetwp-number facetwp-number-max" value="' . esc_attr( $value[1] ) . '" placeholder="' . __( 'Max', 'fwp-front' ) . '" />';
}
$output .= '<input type="button" class="facetwp-submit" value="' . __( 'Go', 'fwp-front' ) . '" />';
return $output;
}
/**
* Filter the query based on selected values
*/
function filter_posts( $params ) {
global $wpdb;
$facet = $params['facet'];
$values = $params['selected_values'];
$where = '';
$min = ( '' == $values[0] ) ? false : $values[0];
$max = ( '' == $values[1] ) ? false : $values[1];
$fields = $facet['fields'] ?? 'both';
$compare_type = empty( $facet['compare_type'] ) ? 'basic' : $facet['compare_type'];
$is_dual = ! empty( $facet['source_other'] );
if ( $is_dual && 'basic' != $compare_type ) {
if ( 'exact' == $fields ) {
$max = $min;
}
$min = ( false !== $min ) ? $min : -999999999999;
$max = ( false !== $max ) ? $max : 999999999999;
/**
* Enclose compare
* The post's range must surround the user-defined range
*/
if ( 'enclose' == $compare_type ) {
$where .= " AND (facet_value + 0) <= '$min'";
$where .= " AND (facet_display_value + 0) >= '$max'";
}
/**
* Intersect compare
* @link http://stackoverflow.com/a/325964
*/
if ( 'intersect' == $compare_type ) {
$where .= " AND (facet_value + 0) <= '$max'";
$where .= " AND (facet_display_value + 0) >= '$min'";
}
}
/**
* Basic compare
* The user-defined range must surround the post's range
*/
else {
if ( 'exact' == $fields ) {
$max = $min;
}
if ( false !== $min ) {
$where .= " AND (facet_value + 0) >= '$min'";
}
if ( false !== $max ) {
$where .= " AND (facet_display_value + 0) <= '$max'";
}
}
$sql = "
SELECT DISTINCT post_id FROM {$wpdb->prefix}facetwp_index
WHERE facet_name = '{$facet['name']}' $where";
return facetwp_sql( $sql, $facet );
}
function register_fields() {
return [
'number_fields' => [
'type' => 'alias',
'items' => [
'fields' => [
'type' => 'select',
'label' => __( 'Fields to show', 'fwp' ),
'choices' => [
'both' => __( 'Min + Max', 'fwp' ),
'exact' => __( 'Exact', 'fwp' ),
'min' => __( 'Min', 'fwp' ),
'max' => __( 'Max', 'fwp' )
]
]
]
]
];
}
/**
* (Front-end) Attach settings to the AJAX response
*/
function settings_js( $params ) {
$facet = $params['facet'];
$fields = empty( $facet['fields'] ) ? 'both' : $facet['fields'];
return [
'fields' => $fields
];
}
}

View File

@@ -0,0 +1,277 @@
<?php
class FacetWP_Facet_Pager extends FacetWP_Facet
{
public $pager_args;
function __construct() {
$this->label = __( 'Pager', 'fwp' );
$this->fields = [ 'pager_type', 'inner_size', 'dots_label', 'prev_label', 'next_label',
'count_text_plural', 'count_text_singular', 'count_text_none', 'load_more_text',
'loading_text', 'default_label', 'per_page_options' ];
}
/**
* Generate the facet HTML
*/
function render( $params ) {
$facet = $params['facet'];
$pager_type = $facet['pager_type'];
$this->pager_args = FWP()->facet->pager_args;
$method = 'render_' . $pager_type;
if ( method_exists( $this, $method ) ) {
return $this->$method( $facet );
}
}
function render_numbers( $facet ) {
$inner_size = (int) $facet['inner_size'];
$dots_label = facetwp_i18n( $facet['dots_label'] );
$prev_label = facetwp_i18n( $facet['prev_label'] );
$next_label = facetwp_i18n( $facet['next_label'] );
$output = '';
$page = (int) $this->pager_args['page'];
$total_pages = (int) $this->pager_args['total_pages'];
$inner_first = max( $page - $inner_size, 2 );
$inner_last = min( $page + $inner_size, $total_pages - 1 );
if ( 1 < $total_pages ) {
// Prev button
if ( 1 < $page && '' != $prev_label ) {
$output .= $this->render_page( $page - 1, $prev_label, 'prev' );
}
// First page
$output .= $this->render_page( 1, false, 'first' );
// Dots
if ( 2 < $inner_first && '' != $dots_label ) {
$output .= $this->render_page( '', $dots_label, 'dots' );
}
for ( $i = $inner_first; $i <= $inner_last; $i++ ) {
$output .= $this->render_page( $i );
}
// Dots
if ( $inner_last < $total_pages - 1 && '' != $dots_label ) {
$output .= $this->render_page( '', $dots_label, 'dots' );
}
// Last page
$output .= $this->render_page( $total_pages, false, 'last' );
// Next button
if ( $page < $total_pages && '' != $next_label ) {
$output .= $this->render_page( $page + 1, $next_label, 'next' );
}
}
return '<div class="facetwp-pager">' . $output . '</div>';
}
function render_page( $page, $label = false, $extra_class = false ) {
$label = ( false === $label ) ? $page : $label;
$class = 'facetwp-page';
if ( ! empty( $extra_class ) ) {
$class .= ' ' . $extra_class;
}
if ( $page == $this->pager_args['page'] ) {
$class .= ' active';
}
$data = empty( $page ) ? '' : ' data-page="' . $page . '"';
$html = '<a class="' . $class . '"' . $data . '>' . $label . '</a>';
return apply_filters( 'facetwp_facet_pager_link', $html, [
'page' => $page,
'label' => $label,
'extra_class' => $extra_class
]);
}
function render_counts( $facet ) {
$text_singular = facetwp_i18n( $facet['count_text_singular'] );
$text_plural = facetwp_i18n( $facet['count_text_plural'] );
$text_none = facetwp_i18n( $facet['count_text_none'] );
$page = $this->pager_args['page'];
$per_page = $this->pager_args['per_page'];
$total_rows = $this->pager_args['total_rows'];
$total_pages = $this->pager_args['total_pages'];
if ( -1 == $per_page ) {
$per_page = $total_rows;
}
if ( 1 < $total_rows ) {
$lower = ( 1 + ( ( $page - 1 ) * $per_page ) );
$upper = ( $page * $per_page );
$upper = ( $total_rows < $upper ) ? $total_rows : $upper;
// If a load_more pager is in use, force $lower = 1
if ( FWP()->helper->facet_setting_exists( 'pager_type', 'load_more' ) ) {
$lower = 1;
}
$output = $text_plural;
$output = str_replace( '[lower]', $lower, $output );
$output = str_replace( '[upper]', $upper, $output );
$output = str_replace( '[total]', $total_rows, $output );
$output = str_replace( '[page]', $page, $output );
$output = str_replace( '[per_page]', $per_page, $output );
$output = str_replace( '[total_pages]', $total_pages, $output );
}
else {
$output = ( 0 < $total_rows ) ? $text_singular : $text_none;
}
return $output;
}
function render_load_more( $facet ) {
$text = facetwp_i18n( $facet['load_more_text'] );
$loading_text = facetwp_i18n( $facet['loading_text'] );
return '<button class="facetwp-load-more" data-loading="' . esc_attr( $loading_text ) . '">' . esc_attr( $text ) . '</button>';
}
function render_per_page( $facet ) {
$default = facetwp_i18n( $facet['default_label'] );
$options = explode( ',', $facet['per_page_options'] );
$options = array_map( 'trim', $options );
$output = '';
if ( ! empty( $default ) ) {
$output .= '<option value="">' . esc_attr( $default ) . '</option>';
}
$per_page = $this->pager_args['per_page'];
$var_exists = isset( FWP()->request->url_vars['per_page'] );
foreach ( $options as $option ) {
$val = $label = $option;
// Support "All" option
if ( ! ctype_digit( $val ) ) {
$val = -1;
$label = facetwp_i18n( $label );
}
$selected = ( $var_exists && $val == $per_page ) ? ' selected' : '';
$output .= '<option value="' . $val . '"' . $selected . '>' . esc_attr( $label ) . '</option>';
}
return '<select class="facetwp-per-page-select">' . $output . '</select>';
}
/**
* Filter the query based on selected values
*/
function filter_posts( $params ) {
return 'continue';
}
/**
* (Front-end) Attach settings to the AJAX response
*/
function settings_js( $params ) {
$facet = $params['facet'];
return [
'pager_type' => $facet['pager_type']
];
}
function register_fields() {
return [
'pager_type' => [
'type' => 'select',
'label' => __( 'Pager type', 'fwp' ),
'choices' => [
'numbers' => __( 'Page numbers', 'fwp' ),
'counts' => __( 'Result counts', 'fwp' ),
'load_more' => __( 'Load more', 'fwp' ),
'per_page' => __( 'Per page', 'fwp' )
]
],
'inner_size' => [
'label' => __( 'Inner size', 'fwp' ),
'notes' => 'Number of pages to show on each side of the current page',
'default' => 2,
'show' => "facet.pager_type == 'numbers'"
],
'dots_label' => [
'label' => __( 'Dots label', 'fwp' ),
'notes' => 'The filler between the inner and outer pages',
'default' => '…',
'show' => "facet.pager_type == 'numbers'"
],
'prev_label' => [
'label' => __( 'Prev button label', 'fwp' ),
'notes' => 'Leave blank to hide',
'default' => '« Prev',
'show' => "facet.pager_type == 'numbers'"
],
'next_label' => [
'label' => __( 'Next button label', 'fwp' ),
'notes' => 'Leave blank to hide',
'default' => 'Next »',
'show' => "facet.pager_type == 'numbers'"
],
'count_text_plural' => [
'label' => __( 'Count text (plural)', 'fwp' ),
'notes' => 'Available tags: [lower], [upper], [total], [page], [per_page], [total_pages]',
'default' => '[lower] - [upper] of [total] results',
'show' => "facet.pager_type == 'counts'"
],
'count_text_singular' => [
'label' => __( 'Count text (singular)', 'fwp' ),
'default' => '1 result',
'show' => "facet.pager_type == 'counts'"
],
'count_text_none' => [
'label' => __( 'Count text (no results)', 'fwp' ),
'default' => 'No results',
'show' => "facet.pager_type == 'counts'"
],
'load_more_text' => [
'label' => __( 'Load more text', 'fwp' ),
'default' => 'Load more',
'show' => "facet.pager_type == 'load_more'"
],
'loading_text' => [
'label' => __( 'Loading text', 'fwp' ),
'default' => 'Loading...',
'show' => "facet.pager_type == 'load_more'"
],
'default_label' => [
'label' => __( 'Default label', 'fwp' ),
'default' => 'Per page',
'show' => "facet.pager_type == 'per_page'"
],
'per_page_options' => [
'label' => __( 'Per page options', 'fwp' ),
'notes' => 'A comma-separated list of choices. Optionally add a non-numeric choice to be used as a "Show all" option.',
'default' => '10, 25, 50, 100',
'show' => "facet.pager_type == 'per_page'"
]
];
}
}

View File

@@ -0,0 +1,354 @@
<?php
class FacetWP_Facet_Proximity_Core extends FacetWP_Facet
{
/* (array) Associative array containing post_id => distance */
public $distance = [];
/* (array) Associative array containing post_id => [lat, lng] */
public $post_latlng = [];
function __construct() {
$this->label = __( 'Proximity', 'fwp' );
$this->fields = [ 'longitude', 'unit', 'radius_ui', 'radius_options', 'radius_min', 'radius_max', 'radius_default' ];
add_filter( 'facetwp_index_row', [ $this, 'index_latlng' ], 1, 2 );
add_filter( 'facetwp_sort_options', [ $this, 'sort_options' ], 1, 2 );
add_filter( 'facetwp_filtered_post_ids', [ $this, 'sort_by_distance' ], 10, 2 );
}
/**
* Generate the facet HTML
*/
function render( $params ) {
$output = '';
$facet = $params['facet'];
$value = $params['selected_values'];
$unit = empty( $facet['unit'] ) ? 'mi' : $facet['unit'];
$lat = empty( $value[0] ) ? '' : $value[0];
$lng = empty( $value[1] ) ? '' : $value[1];
$chosen_radius = empty( $value[2] ) ? '' : (float) $value[2];
$location_name = empty( $value[3] ) ? '' : urldecode( $value[3] );
$radius_options = [ 10, 25, 50, 100, 250 ];
// Grab the radius UI
$radius_ui = empty( $facet['radius_ui'] ) ? 'dropdown' : $facet['radius_ui'];
// Grab radius options from the UI
if ( ! empty( $facet['radius_options'] ) ) {
$radius_options = explode( ',', preg_replace( '/\s+/', '', $facet['radius_options'] ) );
}
// Grab default radius from the UI
if ( empty( $chosen_radius ) && ! empty( $facet['radius_default'] ) ) {
$chosen_radius = (float) $facet['radius_default'];
}
// Support dynamic radius
if ( ! empty( $chosen_radius ) && 0 < $chosen_radius ) {
if ( ! in_array( $chosen_radius, $radius_options ) ) {
$radius_options[] = $chosen_radius;
}
}
$radius_options = apply_filters( 'facetwp_proximity_radius_options', $radius_options );
ob_start();
?>
<span class="facetwp-input-wrap">
<i class="facetwp-icon locate-me"></i>
<input type="text" class="facetwp-location" value="<?php echo esc_attr( $location_name ); ?>" placeholder="<?php _e( 'Enter location', 'fwp-front' ); ?>" autocomplete="off" />
<div class="location-results facetwp-hidden"></div>
</span>
<?php if ( 'dropdown' == $radius_ui ) : ?>
<select class="facetwp-radius facetwp-radius-dropdown">
<?php foreach ( $radius_options as $radius ) : ?>
<?php $selected = ( $chosen_radius == $radius ) ? ' selected' : ''; ?>
<option value="<?php echo $radius; ?>"<?php echo $selected; ?>><?php echo "$radius $unit"; ?></option>
<?php endforeach; ?>
</select>
<?php elseif ( 'slider' == $radius_ui ) : ?>
<div class="facetwp-radius-wrap">
<input class="facetwp-radius facetwp-radius-slider" type="range"
min="<?php echo $facet['radius_min']; ?>"
max="<?php echo $facet['radius_max']; ?>"
value="<?php echo $chosen_radius; ?>"
/>
<div class="facetwp-radius-label">
<span class="facetwp-radius-dist"><?php echo $chosen_radius; ?></span>
<span class="facetwp-radius-unit"><?php echo $facet['unit']; ?></span>
</div>
</div>
<?php elseif ( 'none' == $radius_ui ) : ?>
<input class="facetwp-radius facetwp-hidden" value="<?php echo $chosen_radius; ?>" />
<?php endif; ?>
<input type="hidden" class="facetwp-lat" value="<?php echo esc_attr( $lat ); ?>" />
<input type="hidden" class="facetwp-lng" value="<?php echo esc_attr( $lng ); ?>" />
<?php
return ob_get_clean();
}
/**
* Filter the query based on selected values
*/
function filter_posts( $params ) {
global $wpdb;
$facet = $params['facet'];
$selected_values = $params['selected_values'];
$unit = empty( $facet['unit'] ) ? 'mi' : $facet['unit'];
$earth_radius = ( 'mi' == $unit ) ? 3959 : 6371;
if ( empty( $selected_values ) || empty( $selected_values[0] ) ) {
return 'continue';
}
$lat1 = (float) $selected_values[0];
$lng1 = (float) $selected_values[1];
$radius = (float) $selected_values[2];
$rad = M_PI / 180;
$sql = "
SELECT DISTINCT post_id, facet_value AS `lat`, facet_display_value AS `lng`
FROM {$wpdb->prefix}facetwp_index
WHERE facet_name = '{$facet['name']}'";
$results = $wpdb->get_results( $sql );
foreach ( $results as $row ) {
$lat2 = (float) $row->lat;
$lng2 = (float) $row->lng;
if ( ( $lat1 == $lat2 ) && ( $lng1 == $lng2 ) ) {
$dist = 0;
}
else {
$calc = sin( $lat1 * $rad ) * sin( $lat2 * $rad ) +
cos( $lat1 * $rad ) * cos( $lat2 * $rad ) *
cos( $lng2 * $rad - $lng1 * $rad );
// acos() must be between -1 and 1
$dist = acos( max( -1, min( 1, $calc ) ) ) * $earth_radius;
}
if ( $dist <= $radius ) {
$existing = $this->distance[ $row->post_id ] ?? -1;
if ( -1 == $existing || $dist < $existing ) {
$this->distance[ $row->post_id ] = $dist;
if ( apply_filters( 'facetwp_proximity_store_latlng', false ) ) {
$this->post_latlng[ $row->post_id ] = [ $lat2, $lng2 ];
}
}
}
}
asort( $this->distance, SORT_NUMERIC );
return array_keys( $this->distance );
}
/**
* Output front-end scripts
*/
function front_scripts() {
if ( apply_filters( 'facetwp_proximity_load_js', true ) ) {
// hard-coded
$api_key = defined( 'GMAPS_API_KEY' ) ? GMAPS_API_KEY : '';
// admin ui
$tmp_key = FWP()->helper->get_setting( 'gmaps_api_key' );
$api_key = empty( $tmp_key ) ? $api_key : $tmp_key;
// hook
$api_key = apply_filters( 'facetwp_gmaps_api_key', $api_key );
FWP()->display->assets['gmaps'] = '//maps.googleapis.com/maps/api/js?libraries=places&key=' . trim( $api_key ) . '&callback=Function.prototype';
}
// Pass extra options into Places Autocomplete
$options = apply_filters( 'facetwp_proximity_autocomplete_options', [] );
FWP()->display->json['proximity']['autocomplete_options'] = $options;
FWP()->display->json['proximity']['clearText'] = __( 'Clear location', 'fwp-front' );
FWP()->display->json['proximity']['queryDelay'] = 250;
FWP()->display->json['proximity']['minLength'] = 3;
}
function register_fields() {
return [
'longitude' => [
'type' => 'alias',
'items' => [
'source_other' => [
'label' => __( 'Longitude', 'fwp' ),
'notes' => '(Optional) use a separate longitude field',
'html' => '<data-sources :facet="facet" setting-name="source_other"></data-sources>'
]
]
],
'unit' => [
'type' => 'select',
'label' => __( 'Unit of measurement', 'fwp' ),
'choices' => [
'mi' => __( 'Miles', 'fwp' ),
'km' => __( 'Kilometers', 'fwp' )
]
],
'radius_ui' => [
'type' => 'select',
'label' => __( 'Radius UI', 'fwp' ),
'choices' => [
'dropdown' => __( 'Dropdown', 'fwp' ),
'slider' => __( 'Slider', 'fwp' ),
'none' => __( 'None', 'fwp' )
]
],
'radius_options' => [
'label' => __( 'Radius options', 'fwp' ),
'notes' => 'A comma-separated list of radius choices',
'default' => '10, 25, 50, 100, 250',
'show' => "facet.radius_ui == 'dropdown'"
],
'radius_min' => [
'label' => __( 'Range (min)', 'fwp' ),
'default' => 1,
'show' => "facet.radius_ui == 'slider'"
],
'radius_max' => [
'label' => __( 'Range (max)', 'fwp' ),
'default' => 50,
'show' => "facet.radius_ui == 'slider'"
],
'radius_default' => [
'label' => __( 'Default radius', 'fwp' ),
'default' => 25
]
];
}
/**
* Index the coordinates
* We expect a comma-separated "latitude, longitude"
*/
function index_latlng( $params, $class ) {
$facet = FWP()->helper->get_facet_by_name( $params['facet_name'] );
if ( false !== $facet && 'proximity' == $facet['type'] ) {
$latlng = $params['facet_value'];
// Only handle "lat, lng" strings
if ( ! empty( $latlng ) && is_string( $latlng ) ) {
$latlng = preg_replace( '/[^0-9.,-]/', '', $latlng );
if ( ! empty( $facet['source_other'] ) ) {
$other_params = $params;
$other_params['facet_source'] = $facet['source_other'];
$rows = $class->get_row_data( $other_params );
if ( ! empty( $rows ) && false === strpos( $latlng, ',' ) ) {
$lng = $rows[0]['facet_display_value'];
$lng = preg_replace( '/[^0-9.,-]/', '', $lng );
$latlng .= ',' . $lng;
}
}
if ( preg_match( "/^([\d.-]+),([\d.-]+)$/", $latlng ) ) {
$latlng = explode( ',', $latlng );
$params['facet_value'] = $latlng[0];
$params['facet_display_value'] = $latlng[1];
}
}
}
return $params;
}
/**
* Add "Distance" to the sort box
*/
function sort_options( $options, $params ) {
if ( FWP()->helper->facet_setting_exists( 'type', 'proximity' ) ) {
$options['distance'] = [
'label' => __( 'Distance', 'fwp-front' ),
'query_args' => [
'orderby' => 'post__in',
'order' => 'ASC',
],
];
}
return $options;
}
/**
* Sort the final (filtered) post IDs by distance
*/
function sort_by_distance( $post_ids, $class ) {
$distance = FWP()->helper->facet_types['proximity']->distance;
if ( ! empty( $distance ) ) {
$ordered_posts = array_keys( $distance );
$filtered_posts = array_flip( $post_ids );
$intersected_ids = [];
foreach ( $ordered_posts as $p ) {
if ( isset( $filtered_posts[ $p ] ) ) {
$intersected_ids[] = $p;
}
}
$post_ids = $intersected_ids;
}
return $post_ids;
}
}
/**
* Get a post's distance
*/
function facetwp_get_distance( $post_id = false ) {
global $post;
// Get the post ID
$post_id = ( false === $post_id ) ? $post->ID : $post_id;
// Get the proximity class
$facet_type = FWP()->helper->facet_types['proximity'];
// Get the distance
$distance = $facet_type->distance[ $post_id ] ?? -1;
if ( -1 < $distance ) {
return apply_filters( 'facetwp_proximity_distance_output', $distance );
}
return false;
}

View File

@@ -0,0 +1,64 @@
<?php
class FacetWP_Facet_Radio_Core extends FacetWP_Facet
{
function __construct() {
$this->label = __( 'Radio', 'fwp' );
$this->fields = [ 'label_any', 'parent_term', 'modifiers', 'ghosts', 'orderby', 'count' ];
}
/**
* Load the available choices
*/
function load_values( $params ) {
$params['facet']['operator'] = 'or';
return FWP()->helper->facet_types['checkboxes']->load_values( $params );
}
/**
* Generate the facet HTML
*/
function render( $params ) {
$output = '';
$facet = $params['facet'];
$values = (array) $params['values'];
$selected_values = (array) $params['selected_values'];
$label_any = empty( $facet['label_any'] ) ? false : facetwp_i18n( $facet['label_any'] );
if ( $label_any ) {
$selected = empty( $selected_values ) ? ' checked' : '';
$output .= '<div class="facetwp-radio' . $selected . '" data-value="">' . esc_attr( $label_any ) . '</div>';
}
foreach ( $values as $row ) {
$label = esc_html( $row['facet_display_value'] );
$selected = in_array( $row['facet_value'], $selected_values ) ? ' checked' : '';
$selected .= ( 0 == $row['counter'] && '' == $selected ) ? ' disabled' : '';
$output .= '<div class="facetwp-radio' . $selected . '" data-value="' . esc_attr( $row['facet_value'] ) . '">';
$output .= '<span class="facetwp-display-value">';
$output .= apply_filters( 'facetwp_facet_display_value', $label, [
'selected' => ( '' !== $selected ),
'facet' => $facet,
'row' => $row
]);
$output .= '</span>';
$output .= '<span class="facetwp-counter">(' . $row['counter'] . ')</span>';
$output .= '</div>';
}
return $output;
}
/**
* Filter the query based on selected values
*/
function filter_posts( $params ) {
$params['facet']['operator'] = 'or';
return FWP()->helper->facet_types['checkboxes']->filter_posts( $params );
}
}

View File

@@ -0,0 +1,114 @@
<?php
class FacetWP_Facet_Rating extends FacetWP_Facet
{
function __construct() {
$this->label = __( 'Star Rating', 'fwp' );
$this->fields = [];
}
/**
* Load the available choices
*/
function load_values( $params ) {
global $wpdb;
$facet = $params['facet'];
$from_clause = $wpdb->prefix . 'facetwp_index f';
// Facet in "OR" mode
$where_clause = $this->get_where_clause( $facet );
$output = [
1 => [ 'counter' => 0 ],
2 => [ 'counter' => 0 ],
3 => [ 'counter' => 0 ],
4 => [ 'counter' => 0 ],
5 => [ 'counter' => 0 ]
];
$sql = "
SELECT COUNT(*) AS `count`, FLOOR(f.facet_value) AS `rating`
FROM $from_clause
WHERE f.facet_name = '{$facet['name']}' AND FLOOR(f.facet_value) >= 1 $where_clause
GROUP BY rating";
$results = $wpdb->get_results( $sql );
foreach ( $results as $result ) {
$output[ $result->rating ]['counter'] = $result->count;
}
$total = 0;
// The lower rating should include higher rating counts
for ( $i = 5; $i > 0; $i-- ) {
$output[ $i ]['counter'] += $total;
$total = $output[ $i ]['counter'];
}
return $output;
}
/**
* Generate the facet HTML
*/
function render( $params ) {
$output = '';
$facet = $params['facet'];
$values = (array) $params['values'];
$selected_values = (array) $params['selected_values'];
$num_stars = 0;
foreach ( $values as $val ) {
if ( 0 < $val['counter'] ) {
$num_stars++;
}
}
if ( 0 < $num_stars ) {
$output .= '<span class="facetwp-stars">';
for ( $i = $num_stars; $i >= 1; $i-- ) {
$class = in_array( $i, $selected_values ) ? ' selected' : '';
$output .= '<span class="facetwp-star' . $class . '" data-value="' . $i . '" data-counter="' . $values[ $i ]['counter'] . '">&#9733;</span>';
}
$output .= '</span>';
$output .= ' <span class="facetwp-star-label"></span>';
$output .= ' <span class="facetwp-counter"></span>';
}
return $output;
}
/**
* Filter the query based on selected values
*/
function filter_posts( $params ) {
global $wpdb;
$facet = $params['facet'];
$selected_values = $params['selected_values'];
$selected_values = is_array( $selected_values ) ? $selected_values[0] : $selected_values;
$sql = "
SELECT DISTINCT post_id FROM {$wpdb->prefix}facetwp_index
WHERE facet_name = '{$facet['name']}' AND facet_value >= '$selected_values'";
return $wpdb->get_col( $sql );
}
/**
* Output front-end scripts
*/
function front_scripts() {
FWP()->display->json['rating']['& up'] = __( '& up', 'fwp-front' );
FWP()->display->json['rating']['Undo'] = __( 'Undo', 'fwp-front' );
}
}

View File

@@ -0,0 +1,85 @@
<?php
class FacetWP_Facet_Reset extends FacetWP_Facet
{
function __construct() {
$this->label = __( 'Reset', 'fwp' );
$this->fields = [ 'reset_ui', 'reset_text', 'reset_mode', 'reset_facets', 'auto_hide' ];
}
function render( $params ) {
$facet = $params['facet'];
$reset_ui = $facet['reset_ui'];
$reset_text = empty( $facet['reset_text'] ) ? __( 'Reset', 'fwp-front' ) : $facet['reset_text'];
$reset_text = facetwp_i18n( $reset_text );
$classes = [ 'facetwp-reset' ];
$attrs = '';
if ( ! FWP()->helper->facet_is( $facet, 'reset_mode', 'off' ) ) {
if ( ! empty( $facet['reset_facets'] ) ) {
$vals = implode( ',', $facet['reset_facets'] );
$attrs = ' data-mode="{mode}" data-values="{vals}"';
$attrs = str_replace( '{mode}', $facet['reset_mode'], $attrs );
$attrs = str_replace( '{vals}', esc_attr( $vals ), $attrs );
}
}
if ( FWP()->helper->facet_is( $facet, 'auto_hide', 'yes' ) ) {
$classes[] = 'facetwp-hide-empty';
}
if ( 'button' == $reset_ui ) {
$output = '<button class="{classes}"{attrs}>{label}</button>';
}
else {
$output = '<a class="{classes}" href="javascript:;"{attrs}>{label}</a>';
}
$output = str_replace( '{classes}', implode( ' ', $classes ), $output );
$output = str_replace( '{label}', esc_attr( $reset_text ), $output );
$output = str_replace( '{attrs}', $attrs, $output );
return $output;
}
function filter_posts( $params ) {
return 'continue';
}
function register_fields() {
return [
'reset_ui' => [
'type' => 'select',
'label' => __( 'Reset UI', 'fwp' ),
'choices' => [
'button' => __( 'Button', 'fwp' ),
'link' => __( 'Link', 'fwp' )
]
],
'reset_mode' => [
'type' => 'select',
'label' => __( 'Include / exclude', 'fwp' ),
'notes' => 'Include or exclude certain facets?',
'choices' => [
'off' => __( 'Reset everything', 'fwp' ),
'include' => __( 'Reset only these facets', 'fwp' ),
'exclude' => __( 'Reset all except these facets', 'fwp' )
]
],
'reset_facets' => [
'label' => '',
'html' => '<facet-names :facet="facet" setting="reset_facets"></facet-names>',
'show' => "facet.reset_mode != 'off'"
],
'auto_hide' => [
'type' => 'toggle',
'label' => __( 'Auto-hide', 'fwp' ),
'notes' => 'Hide when no facets have selected values'
]
];
}
}

View File

@@ -0,0 +1,89 @@
<?php
class FacetWP_Facet_Search extends FacetWP_Facet
{
function __construct() {
$this->label = __( 'Search', 'fwp' );
$this->fields = [ 'search_engine', 'placeholder', 'auto_refresh' ];
}
/**
* Generate the facet HTML
*/
function render( $params ) {
$output = '';
$facet = $params['facet'];
$value = (array) $params['selected_values'];
$value = empty( $value ) ? '' : stripslashes( $value[0] );
$placeholder = empty( $facet['placeholder'] ) ? __( 'Enter keywords', 'fwp-front' ) : $facet['placeholder'];
$placeholder = facetwp_i18n( $placeholder );
$output .= '<span class="facetwp-input-wrap">';
$output .= '<i class="facetwp-icon"></i>';
$output .= '<input type="text" class="facetwp-search" value="' . esc_attr( $value ) . '" placeholder="' . esc_attr( $placeholder ) . '" autocomplete="off" />';
$output .= '</span>';
return $output;
}
/**
* Filter the query based on selected values
*/
function filter_posts( $params ) {
$facet = $params['facet'];
$selected_values = $params['selected_values'];
$selected_values = is_array( $selected_values ) ? $selected_values[0] : $selected_values;
if ( empty( $selected_values ) ) {
return 'continue';
}
// Default WP search
$search_args = [
's' => $selected_values,
'posts_per_page' => 200,
'fields' => 'ids',
];
$search_args = apply_filters( 'facetwp_search_query_args', $search_args, $params );
$query = new WP_Query( $search_args );
return (array) $query->posts;
}
function register_fields() {
$engines = apply_filters( 'facetwp_facet_search_engines', [] );
$choices = [ '' => __( 'WP Default', 'fwp' ) ];
foreach ( $engines as $key => $label ) {
$choices[ $key ] = $label;
}
return [
'search_engine' => [
'type' => 'select',
'label' => __( 'Search engine', 'fwp' ),
'choices' => $choices
],
'auto_refresh' => [
'type' => 'toggle',
'label' => __( 'Auto refresh', 'fwp' ),
'notes' => 'Automatically refresh the results while typing?'
]
];
}
/**
* (Front-end) Attach settings to the AJAX response
*/
function settings_js( $params ) {
$auto_refresh = empty( $params['facet']['auto_refresh'] ) ? 'no' : $params['facet']['auto_refresh'];
return [ 'auto_refresh' => $auto_refresh ];
}
}

View File

@@ -0,0 +1,160 @@
<?php
class FacetWP_Facet_Slider extends FacetWP_Facet
{
function __construct() {
$this->label = __( 'Slider', 'fwp' );
$this->fields = [ 'source_other', 'compare_type', 'prefix', 'suffix',
'reset_text', 'slider_format', 'step' ];
add_filter( 'facetwp_render_output', [ $this, 'maybe_prevent_facet_html' ], 10, 2 );
}
/**
* Generate the facet HTML
*/
function render( $params ) {
$facet = $params['facet'];
$reset_text = __( 'Reset', 'fwp-front' );
if ( ! empty( $facet['reset_text'] ) ) {
$reset_text = facetwp_i18n( $facet['reset_text'] );
}
$output = '<div class="facetwp-slider-wrap">';
$output .= '<div class="facetwp-slider"></div>';
$output .= '</div>';
$output .= '<span class="facetwp-slider-label"></span>';
$output .= '<div><input type="button" class="facetwp-slider-reset" value="' . esc_attr( $reset_text ) . '" /></div>';
return $output;
}
/**
* Filter the query based on selected values
*/
function filter_posts( $params ) {
return FWP()->helper->facet_types['number_range']->filter_posts( $params );
}
/**
* (Front-end) Attach settings to the AJAX response
*/
function settings_js( $params ) {
global $wpdb;
$facet = $params['facet'];
$where_clause = $this->get_where_clause( $facet );
$selected_values = $params['selected_values'];
// Set default slider values
$defaults = [
'format' => '',
'prefix' => '',
'suffix' => '',
'step' => 1,
];
$facet = array_merge( $defaults, $facet );
$sql = "
SELECT MIN(facet_value + 0) AS `min`, MAX(facet_display_value + 0) AS `max` FROM {$wpdb->prefix}facetwp_index
WHERE facet_name = '{$facet['name']}' AND facet_display_value != '' $where_clause";
$row = $wpdb->get_row( $sql );
$range_min = (float) $row->min;
$range_max = (float) $row->max;
$selected_min = (float) ( $selected_values[0] ?? $range_min );
$selected_max = (float) ( $selected_values[1] ?? $range_max );
return [
'range' => [ // outer (bar)
'min' => min( $range_min, $selected_min ),
'max' => max( $range_max, $selected_max )
],
'decimal_separator' => FWP()->helper->get_setting( 'decimal_separator' ),
'thousands_separator' => FWP()->helper->get_setting( 'thousands_separator' ),
'start' => [ $selected_min, $selected_max ], // inner (handles)
'format' => $facet['format'],
'prefix' => facetwp_i18n( $facet['prefix'] ),
'suffix' => facetwp_i18n( $facet['suffix'] ),
'step' => $facet['step']
];
}
/**
* Prevent the slider HTML from refreshing when active
* @since 3.8.11
*/
function maybe_prevent_facet_html( $output, $params ) {
if ( ! empty( $output['facets'] && 0 === $params['first_load' ] ) ) {
foreach ( FWP()->facet->facets as $name => $facet ) {
if ( 'slider' == $facet['type'] && ! empty( $facet['selected_values'] ) ) {
unset( $output['facets'][ $name ] );
}
}
}
return $output;
}
/**
* Output any front-end scripts
*/
function front_scripts() {
FWP()->display->assets['nouislider.css'] = FACETWP_URL . '/assets/vendor/noUiSlider/nouislider.css';
FWP()->display->assets['nouislider.js'] = FACETWP_URL . '/assets/vendor/noUiSlider/nouislider.min.js';
FWP()->display->assets['nummy.js'] = FACETWP_URL . '/assets/vendor/nummy/nummy.min.js';
}
function register_fields() {
$thousands = FWP()->helper->get_setting( 'thousands_separator' );
$decimal = FWP()->helper->get_setting( 'decimal_separator' );
$choices = [];
if ( '' != $thousands ) {
$choices['0,0'] = "5{$thousands}280";
$choices['0,0.0'] = "5{$thousands}280{$decimal}4";
$choices['0,0.00'] = "5{$thousands}280{$decimal}42";
}
$choices['0'] = '5280';
$choices['0.0'] = "5280{$decimal}4";
$choices['0.00'] = "5280{$decimal}42";
$choices['0a'] = '5k';
$choices['0.0a'] = "5{$decimal}3k";
$choices['0.00a'] = "5{$decimal}28k";
return [
'prefix' => [
'label' => __( 'Prefix', 'fwp' ),
'notes' => 'Text that appears before each slider value',
],
'suffix' => [
'label' => __( 'Suffix', 'fwp' ),
'notes' => 'Text that appears after each slider value',
],
'slider_format' => [
'type' => 'alias',
'items' => [
'format' => [
'type' => 'select',
'label' => __( 'Format', 'fwp' ),
'notes' => 'If the number separators are wrong, change the [Separators] setting in the Settings tab, then save and reload the page',
'choices' => $choices
]
]
],
'step' => [
'label' => __( 'Step', 'fwp' ),
'notes' => 'The amount of increase between intervals',
'default' => 1
]
];
}
}

View File

@@ -0,0 +1,226 @@
<?php
class FacetWP_Facet_Sort extends FacetWP_Facet
{
public $sort_options = [];
function __construct() {
$this->label = __( 'Sort', 'fwp' );
$this->fields = [ 'sort_default_label', 'sort_options' ];
add_filter( 'facetwp_filtered_query_args', [ $this, 'apply_sort' ], 1, 2 );
add_filter( 'facetwp_render_output', [ $this, 'render_sort_feature' ], 1, 2 );
}
/**
* Render the sort facet
*/
function render( $params ) {
$facet = $this->parse_sort_facet( $params['facet'] );
$selected_values = (array) $params['selected_values'];
$label = facetwp_i18n( $facet['default_label'] );
$output = '<option value="">' . esc_attr( $label ) . '</option>';
foreach ( $facet['sort_options'] as $key => $choice ) {
$label = facetwp_i18n( $choice['label'] );
$selected = in_array( $key, $selected_values ) ? ' selected' : '';
$output .= '<option value="' . esc_attr( $key ) . '"' . $selected . '>' . esc_attr( $label ) . '</option>';
}
return '<select>' . $output . '</select>';
}
/**
* Sort facets don't narrow results
*/
function filter_posts( $params ) {
return 'continue';
}
/**
* Register admin settings
*/
function register_fields() {
return [
'sort_default_label' => [
'type' => 'alias',
'items' => [
'default_label' => [
'label' => __( 'Default label', 'fwp' ),
'notes' => 'The sort box placeholder text',
'default' => __( 'Sort by', 'fwp' )
]
]
],
'sort_options' => [
'label' => __( 'Sort options', 'fwp' ),
'notes' => 'Define the choices that appear in the sort box',
'html' => '<sort-options :facet="facet"></sort-options><input type="hidden" class="facet-sort-options" value="[]" />'
]
];
}
/**
* Convert a sort facet's sort options into WP_Query arguments
* @since 4.0.8
*/
function parse_sort_facet( $facet ) {
$sort_options = [];
foreach ( $facet['sort_options'] as $row ) {
$parsed = FWP()->builder->parse_query_obj([ 'orderby' => $row['orderby'] ]);
$sort_options[ $row['name'] ] = [
'label' => $row['label'],
'query_args' => array_intersect_key( $parsed, [
'meta_query' => true,
'orderby' => true
])
];
}
$sort_options = apply_filters( 'facetwp_facet_sort_options', $sort_options, [
'facet' => $facet,
'template_name' => FWP()->facet->template['name']
]);
$facet['sort_options'] = $sort_options;
return $facet;
}
/**
* Handle both sort facets and the (old) sort feature
* @since 4.0.6
*/
function apply_sort( $query_args, $class ) {
foreach ( $class->facets as $facet ) {
if ( 'sort' == $facet['type'] ) {
$sort_facet = $this->parse_sort_facet( $facet );
break;
}
}
// Support the (old) sort feature
$sort_value = 'default';
$this->sort_options = $this->get_sort_options();
if ( ! empty( $class->ajax_params['extras']['sort'] ) ) {
$sort_value = $class->ajax_params['extras']['sort'];
if ( ! empty( $this->sort_options[ $sort_value ] ) ) {
$args = $this->sort_options[ $sort_value ]['query_args'];
$query_args = array_merge( $query_args, $args );
}
}
// Preserve relevancy sort
$use_relevancy = apply_filters( 'facetwp_use_search_relevancy', true, $class );
$is_default_sort = ( 'default' == $sort_value && empty( $class->http_params['get']['orderby'] ) );
if ( $class->is_search && $use_relevancy && $is_default_sort && FWP()->is_filtered ) {
$query_args['orderby'] = 'post__in';
}
// Support the (new) sort facet
if ( ! empty( $sort_facet['selected_values'] ) ) {
$chosen = $sort_facet['selected_values'][0];
$sort_options = $sort_facet['sort_options'];
if ( isset( $sort_options[ $chosen ] ) ) {
$qa = $sort_options[ $chosen ]['query_args'];
if ( isset( $qa['meta_query'] ) ) {
$meta_query = $query_args['meta_query'] ?? [];
$query_args['meta_query'] = array_merge( $meta_query, $qa['meta_query'] );
}
$query_args['orderby'] = $qa['orderby'];
}
}
return $query_args;
}
/**
* Generate choices for the (old) sort feature
* @since 4.0.6
*/
function get_sort_options() {
$options = [
'default' => [
'label' => __( 'Sort by', 'fwp-front' ),
'query_args' => []
],
'title_asc' => [
'label' => __( 'Title (A-Z)', 'fwp-front' ),
'query_args' => [
'orderby' => 'title',
'order' => 'ASC',
]
],
'title_desc' => [
'label' => __( 'Title (Z-A)', 'fwp-front' ),
'query_args' => [
'orderby' => 'title',
'order' => 'DESC',
]
],
'date_desc' => [
'label' => __( 'Date (Newest)', 'fwp-front' ),
'query_args' => [
'orderby' => 'date',
'order' => 'DESC',
]
],
'date_asc' => [
'label' => __( 'Date (Oldest)', 'fwp-front' ),
'query_args' => [
'orderby' => 'date',
'order' => 'ASC',
]
]
];
return apply_filters( 'facetwp_sort_options', $options, [
'template_name' => FWP()->facet->template['name'],
] );
}
/**
* Render the (old) sort feature
* @since 4.0.6
*/
function render_sort_feature( $output, $params ) {
$has_sort = isset( $params['extras']['sort'] );
$has_choices = isset( $this->sort_options );
if ( 0 == $params['soft_refresh'] && $has_sort && $has_choices ) {
$html = '';
foreach ( $this->sort_options as $key => $atts ) {
$html .= '<option value="' . $key . '">' . $atts['label'] . '</option>';
}
$html = '<select class="facetwp-sort-select">' . $html . '</select>';
$output['sort'] = apply_filters( 'facetwp_sort_html', $html, [
'sort_options' => $this->sort_options,
'template_name' => FWP()->facet->template['name'],
]);
}
return $output;
}
}

View File

@@ -0,0 +1,43 @@
<?php
// DO NOT MODIFY THIS FILE!
// Use your theme's functions.php instead
/**
* An alternate to using do_shortcode()
*
* facetwp_display( 'pager' );
* facetwp_display( 'template', 'cars' );
* facetwp_display( 'template', 'cars', [ 'static' => true ] );
*
* @since 1.7.5
*/
function facetwp_display() {
$args = array_replace( [ 'pager', true, [] ], func_get_args() );
$atts = (array) $args[2];
$atts[ $args[0] ] = $args[1];
return FWP()->display->shortcode( $atts );
}
/**
* Allow for translation of dynamic strings
* @since 2.1
*/
function facetwp_i18n( $string ) {
return apply_filters( 'facetwp_i18n', $string );
}
/**
* Support SQL modifications
* @since 2.7
*/
function facetwp_sql( $sql, $facet ) {
global $wpdb;
$sql = apply_filters( 'facetwp_wpdb_sql', $sql, $facet );
return apply_filters( 'facetwp_wpdb_get_col', $wpdb->get_col( $sql ), $sql, $facet );
}

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();
}