mirror of
https://github.com/proelements/proelements.git
synced 2026-04-05 20:13:47 +00:00
v3.33.1
This commit is contained in:
19
modules/loop-filter/data/controller.php
Normal file
19
modules/loop-filter/data/controller.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
namespace ElementorPro\Modules\LoopFilter\Data;
|
||||
|
||||
use ElementorPro\Core\Data\Controller as Controller_Base;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Controller extends Controller_Base {
|
||||
public function get_name() {
|
||||
return 'loop-filter';
|
||||
}
|
||||
|
||||
protected function register_endpoints() {
|
||||
$this->register_endpoint( Endpoints\Get_Post_Type_Taxonomies::class );
|
||||
$this->register_endpoint( Endpoints\Refresh_Loop::class );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
namespace ElementorPro\Modules\LoopFilter\Data\Endpoints;
|
||||
|
||||
use ElementorPro\Modules\LoopFilter\Traits\Taxonomy_Filter_Trait;
|
||||
use ElementorPro\Core\Data\Endpoints\Refresh_Base;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
// Create a class that extends the Base Endpoint class.
|
||||
// This class should handle fetching taxonomies from the database, it registers an endpoint that can be accessed via the REST API.
|
||||
// The endpoint accepts a string argument of 'post_type' and returns an array of taxonomies for that post type.
|
||||
|
||||
class Get_Post_Type_Taxonomies extends Refresh_Base {
|
||||
|
||||
use Taxonomy_Filter_Trait;
|
||||
|
||||
public function get_name() : string {
|
||||
return 'get_post_type_taxonomies';
|
||||
}
|
||||
|
||||
public function get_route() : string {
|
||||
return 'get-post-type-taxonomies';
|
||||
}
|
||||
|
||||
protected function permission_callback( $request, $widget_name = '' ) : bool {
|
||||
return current_user_can( 'edit_posts' );
|
||||
}
|
||||
|
||||
public function get_items( \WP_REST_Request $request ): array {
|
||||
$data = $request->get_params();
|
||||
|
||||
return $this->get_taxonomy_options( [ $data['post_type'] ] );
|
||||
}
|
||||
|
||||
protected function register() {
|
||||
register_rest_route( $this->controller->get_namespace(), $this->get_route(), [
|
||||
[
|
||||
'args' => [
|
||||
'post_type' => [
|
||||
'description' => 'The post type for which to fetch the list of taxonomies.',
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'validate_callback' => function ( $param ) {
|
||||
return ! empty( $param ) && is_string( $param );
|
||||
},
|
||||
],
|
||||
],
|
||||
'methods' => \WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'get_items' ],
|
||||
'permission_callback' => function ( $request ) {
|
||||
return $this->permission_callback( $request );
|
||||
},
|
||||
],
|
||||
] );
|
||||
}
|
||||
}
|
||||
119
modules/loop-filter/data/endpoints/refresh-loop.php
Normal file
119
modules/loop-filter/data/endpoints/refresh-loop.php
Normal file
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
namespace ElementorPro\Modules\LoopFilter\Data\Endpoints;
|
||||
|
||||
use Elementor\Widget_Base;
|
||||
use Elementor\Utils;
|
||||
use ElementorPro\Modules\LoopFilter\Module;
|
||||
use ElementorPro\Plugin;
|
||||
use ElementorPro\Core\Data\Endpoints\Refresh_Base;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Refresh_Loop extends Refresh_Base {
|
||||
public function get_name() : string {
|
||||
return 'refresh-loop';
|
||||
}
|
||||
|
||||
public function get_route() : string {
|
||||
return 'refresh-loop';
|
||||
}
|
||||
|
||||
public function get_updated_loop_widget_markup( \WP_REST_Request $request ): array {
|
||||
$data = $request->get_params();
|
||||
|
||||
/** @var Module $loop_filter */
|
||||
$loop_filter = Plugin::instance()->modules_manager->get_modules( 'loop-filter' );
|
||||
|
||||
$loop_filter->register_widget_filter( $data['widget_id'], $data['widget_filters'] );
|
||||
|
||||
if ( $this->is_edit_mode( $data['post_id'] ) ) {
|
||||
$widget_instance = Plugin::elementor()->elements_manager->create_element_instance( $data['widget_model'] );
|
||||
} else {
|
||||
$widget_instance = $this->create_widget_instance_from_db( $data['post_id'], $data['widget_id'] );
|
||||
}
|
||||
|
||||
set_query_var( 'pagination_base_url', $data['pagination_base_url'] );
|
||||
|
||||
ob_start();
|
||||
|
||||
/** @var Module $loop_filter */
|
||||
$loop_filter = Plugin::instance()->modules_manager->get_modules( 'loop-filter' );
|
||||
|
||||
add_filter( 'elementor/query/query_args', [ $loop_filter, 'filter_loop_query' ], 10, 2 );
|
||||
|
||||
/** @var Widget_Base $widget_instance */
|
||||
$widget_instance->render_content();
|
||||
|
||||
remove_filter( 'elementor/query/query_args', [ $loop_filter, 'filter_loop_query' ] );
|
||||
|
||||
$markup = ob_get_clean();
|
||||
|
||||
set_query_var( 'pagination_base_url', null );
|
||||
|
||||
return [
|
||||
'data' => $markup,
|
||||
];
|
||||
}
|
||||
|
||||
protected function register() {
|
||||
register_rest_route( $this->controller->get_namespace(), $this->get_route(), [
|
||||
[
|
||||
'args' => [
|
||||
'post_id' => [
|
||||
'description' => 'The post ID of the page containing the loop.',
|
||||
'type' => 'integer',
|
||||
'required' => true,
|
||||
'validate_callback' => function ( $param ) {
|
||||
return ! empty( $param ) && is_numeric( $param );
|
||||
},
|
||||
],
|
||||
'widget_id' => [
|
||||
'description' => 'The ID of the loop widget.',
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'validate_callback' => function ( $param ) {
|
||||
return $this->is_widget_id_valid( $param );
|
||||
},
|
||||
],
|
||||
'widget_filters' => [
|
||||
'description' => 'The filters for the loop widget.',
|
||||
'type' => 'object',
|
||||
'required' => true,
|
||||
'validate_callback' => function( $param ) {
|
||||
// TODO: Build a validator for this that iterates over all passed filters and validates them.
|
||||
return is_array( $param );
|
||||
},
|
||||
],
|
||||
'widget_model' => [
|
||||
'description' => 'The model of the loop widget. In Editor mode only.',
|
||||
'type' => 'object',
|
||||
'required' => false,
|
||||
'validate_callback' => function ( $param, $request ) {
|
||||
$params = $request->get_params();
|
||||
|
||||
if ( ! $this->is_edit_mode( $params['post_id'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->is_widget_model_valid( $param );
|
||||
},
|
||||
],
|
||||
'pagination_base_url' => [
|
||||
'required' => false,
|
||||
'validate_callback' => function ( $param ) {
|
||||
return filter_var( $param, FILTER_VALIDATE_URL );
|
||||
},
|
||||
'sanitize_callback' => 'esc_url_raw',
|
||||
],
|
||||
],
|
||||
'methods' => \WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'get_updated_loop_widget_markup' ],
|
||||
'permission_callback' => function ( $request ) {
|
||||
return $this->permission_callback( $request, 'loop-grid' );
|
||||
},
|
||||
],
|
||||
] );
|
||||
}
|
||||
}
|
||||
413
modules/loop-filter/module.php
Normal file
413
modules/loop-filter/module.php
Normal file
@@ -0,0 +1,413 @@
|
||||
<?php
|
||||
namespace ElementorPro\Modules\LoopFilter;
|
||||
|
||||
use ElementorPro\Base\Module_Base;
|
||||
use ElementorPro\Core\Utils;
|
||||
use ElementorPro\Modules\LoopFilter\Query\Taxonomy_Query_Builder;
|
||||
use ElementorPro\Modules\LoopFilter\Query\Data\Query_Constants;
|
||||
use ElementorPro\Modules\LoopFilter\Data\Controller;
|
||||
use ElementorPro\Modules\LoopFilter\Traits\Hierarchical_Taxonomy_Trait;
|
||||
use WP_Term;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Module extends Module_Base {
|
||||
use Hierarchical_Taxonomy_Trait;
|
||||
|
||||
private $operator;
|
||||
private $taxonomy;
|
||||
private $query;
|
||||
|
||||
/**
|
||||
* @var array Array of widgets containing each widget's filters which are tied to the current session.
|
||||
*/
|
||||
private $filters = [];
|
||||
|
||||
protected function get_widgets() {
|
||||
return [ 'Taxonomy_Filter' ];
|
||||
}
|
||||
|
||||
public function get_name() {
|
||||
return 'loop-filter';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base URL for assets.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_assets_base_url(): string {
|
||||
return ELEMENTOR_PRO_URL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register styles.
|
||||
*
|
||||
* At build time, Elementor compiles `/modules/loop-filter/assets/scss/frontend.scss`
|
||||
* to `/assets/css/widget-loop-filter.min.css`.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_styles() {
|
||||
wp_register_style(
|
||||
'widget-loop-filter',
|
||||
$this->get_css_assets_url( 'widget-loop-filter', null, true, true ),
|
||||
[ 'elementor-frontend' ],
|
||||
ELEMENTOR_PRO_VERSION
|
||||
);
|
||||
}
|
||||
|
||||
public function get_post_type_taxonomies( $data ) {
|
||||
if ( ! current_user_can( 'edit_posts' ) || empty( $data['post_type'] ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$post_type_taxonomies = get_object_taxonomies( $data['post_type'], 'objects' );
|
||||
|
||||
$control_options = [];
|
||||
|
||||
foreach ( $post_type_taxonomies as $taxonomy ) {
|
||||
$control_options[ $taxonomy->name ] = $taxonomy->label;
|
||||
}
|
||||
|
||||
return $control_options;
|
||||
}
|
||||
|
||||
public function register_widget_filter( $widget_id, $filter_data ) {
|
||||
if ( empty( $this->filters[ $widget_id ] ) ) {
|
||||
$this->filters[ $widget_id ] = $filter_data;
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $filter_data as $filter_type => $filter ) {
|
||||
if ( empty( $this->filters[ $widget_id ][ $filter_type ] ) ) {
|
||||
$this->filters[ $widget_id ][ $filter_type ] = $filter;
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->filters[ $widget_id ][ $filter_type ] = array_merge( $this->filters[ $widget_id ][ $filter_type ], $filter );
|
||||
}
|
||||
}
|
||||
|
||||
public function filter_loop_query( $query_args, $widget ) {
|
||||
$widget_id = $widget->get_id();
|
||||
|
||||
if ( empty( $this->filters[ $widget_id ] ) ) {
|
||||
return $query_args;
|
||||
}
|
||||
|
||||
/** @var array $filter_types An array containing all of a widget's different filters - e.g. taxonomy, price, rating... */
|
||||
$filter_types = $this->filters[ $widget_id ];
|
||||
|
||||
// TODO: This part is non-generic and should be refactored to allow for multiple types of filters.
|
||||
$query_args['tax_query']['relation'] = $this->query['AND']['relation'];
|
||||
|
||||
foreach ( $filter_types as $filters ) {
|
||||
// The $filters array contains all filters of a specific type. For example, for the taxonomy filter type,
|
||||
// it would contain all taxonomies to be filtered - e.g. 'category', 'tag', 'product-cat', etc.
|
||||
$tax_query = [];
|
||||
|
||||
foreach ( $filters as $filter_taxonomy => $filter ) {
|
||||
if ( 'logicalJoin' === $filter_taxonomy ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $this->is_filter_empty( $filter ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Sanitize request data.
|
||||
$taxonomy = sanitize_key( $filter_taxonomy );
|
||||
( new Taxonomy_Query_Builder() )->get_merged_queries( $tax_query, $taxonomy, $filter );
|
||||
}
|
||||
}
|
||||
|
||||
$query_args['tax_query'] = array_merge( $query_args['tax_query'], $tax_query ?? [] );
|
||||
|
||||
return $query_args;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Check if the filter is empty.
|
||||
* Taxonomy Filter URL parameter is empty but not removed i.e. `&e-filter-389c132-product_cat=`.
|
||||
* This edge case happens if a user clears terms and not the Taxonomy filter parameter
|
||||
* @param $filter
|
||||
* @return bool
|
||||
*/
|
||||
public function is_filter_empty( $filter ) {
|
||||
if ( '' === $filter['terms'][0] ) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function add_localize_data( $config ) {
|
||||
$current_query_vars = $this->get_current_query_vars();
|
||||
|
||||
$config['loopFilter'] = [
|
||||
'mainQueryPostType' => $current_query_vars['post_type'] ?? 'post',
|
||||
'nonce' => wp_create_nonce( 'wp_rest' ),
|
||||
];
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
private function get_current_query_vars() {
|
||||
$current_query_vars = $GLOBALS['wp_query']->query_vars;
|
||||
|
||||
/**
|
||||
* Current query variables.
|
||||
*
|
||||
* Filters the query variables for the current query. This hook allows
|
||||
* developers to alter those variables.
|
||||
*
|
||||
* @param array $current_query_vars Current query variables.
|
||||
*/
|
||||
return apply_filters( 'elementor/query/get_query_args/current_query', $current_query_vars );
|
||||
}
|
||||
|
||||
private function parse_query_string( $param_key ) {
|
||||
// Check if the query param is a filter. match a regex for `e-filter-14f9e1d-post_tag` where `14f9e1d` is the widget ID and must be 7 characters long and have only letters and numbers, then followed by a string that can only have letters, numbers, dashes and underscores.
|
||||
if ( ! preg_match( '/^e-filter-[a-z0-9]{7}-[a-z0-9_\-]+$/', $param_key ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Remove the 'filter_' prefix from the query param
|
||||
$filter = str_replace( 'e-filter-', '', $param_key );
|
||||
|
||||
// Split the filter into an array of widget ID and filter type
|
||||
$filter = explode( '-', $filter );
|
||||
|
||||
if ( count( $filter ) !== 2 ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Get the widget ID
|
||||
$widget_id = $filter[0];
|
||||
|
||||
// Get the taxonomy
|
||||
$taxonomy = $filter[1];
|
||||
|
||||
return [
|
||||
'taxonomy' => $taxonomy,
|
||||
'widget_id' => $widget_id,
|
||||
];
|
||||
}
|
||||
|
||||
private function maybe_populate_filters_from_query_string() {
|
||||
if ( ! isset( $_SERVER['QUERY_STRING'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$query_params = [];
|
||||
wp_parse_str( htmlspecialchars_decode( Utils::_unstable_get_super_global_value( $_SERVER, 'QUERY_STRING' ) ), $query_params );
|
||||
|
||||
foreach ( $query_params as $param_key => $param_value ) {
|
||||
// TODO: This part is not generic - it only supports taxonomy filters. It should be refactored to allow for multiple types of filters.
|
||||
$parsed_query_string = $this->parse_query_string( $param_key );
|
||||
|
||||
if ( empty( $parsed_query_string ) || empty( $parsed_query_string['taxonomy'] ) || empty( $parsed_query_string['widget_id'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$terms = $this->get_terms_array_from_params( $param_value );
|
||||
$logical_join = $this->get_logical_join_from_params( $param_value );
|
||||
|
||||
if ( empty( $terms ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$filter_data = [
|
||||
'taxonomy' => [
|
||||
$parsed_query_string['taxonomy'] => [
|
||||
'terms' => $terms,
|
||||
'logicalJoin' => $logical_join,
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$this->register_widget_filter( $parsed_query_string['widget_id'], $filter_data );
|
||||
}
|
||||
}
|
||||
|
||||
private function get_seperator_from_params( $param_value ) {
|
||||
$separator = $this->query['AND']['separator']['from-browser']; // The web browser automatically replaces the plus sign with a space character before sending the data to the server.
|
||||
|
||||
if ( strstr( $param_value, $this->query['OR']['separator']['from-browser'] ) ) {
|
||||
$separator = $this->query['OR']['separator']['from-browser'];
|
||||
$this->operator = $this->query['OR']['operator'];
|
||||
}
|
||||
return $separator;
|
||||
}
|
||||
|
||||
private function get_terms_array_from_params( $param_value ) {
|
||||
$separator = $this->get_seperator_from_params( $param_value );
|
||||
return explode( $separator, $param_value );
|
||||
}
|
||||
|
||||
private function get_logical_join_from_params( $param_value ) {
|
||||
$separator = $this->get_seperator_from_params( $param_value );
|
||||
|
||||
foreach ( $this->query as $index => $data ) {
|
||||
if ( $data['separator']['decoded'] === $separator ) {
|
||||
return $index; // Return the index when the decoded separator is found
|
||||
}
|
||||
}
|
||||
|
||||
return 'AND'; // Default logical join
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function get_query_string_filters() {
|
||||
return $this->filters;
|
||||
}
|
||||
|
||||
public function remove_rest_route_parameter( $link ) {
|
||||
return remove_query_arg( 'rest_route', $link );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_term_not_selected_for_inclusion( $loop_widget_settings, $term, $skin ) {
|
||||
if ( ! $this->is_loop_grid_include_exclude_tax_belong_to_filter_tax( $loop_widget_settings, $term, $skin ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ! empty( $loop_widget_settings[ $skin . '_query_include' ] ) &&
|
||||
in_array( 'terms', $loop_widget_settings[ $skin . '_query_include' ] ) &&
|
||||
isset( $loop_widget_settings[ $skin . '_query_include_term_ids' ] ) &&
|
||||
! in_array( $term->term_id, $loop_widget_settings[ $skin . '_query_include_term_ids' ] );
|
||||
}
|
||||
|
||||
public function is_loop_grid_include_exclude_tax_belong_to_filter_tax( array $loop_widget_settings, WP_Term $term, string $skin ) : bool {
|
||||
$include_exclude_term_ids = array_merge(
|
||||
$loop_widget_settings[ $skin . '_query_include_term_ids' ] ?? [],
|
||||
$loop_widget_settings[ $skin . '_query_exclude_term_ids' ] ?? []
|
||||
);
|
||||
|
||||
foreach ( $include_exclude_term_ids as $term_id ) {
|
||||
$term_to_include_exclude = get_term_by( 'term_taxonomy_id', $term_id );
|
||||
|
||||
if ( $term_to_include_exclude && $term_to_include_exclude->taxonomy === $term->taxonomy ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_term_selected_for_exclusion( $loop_widget_settings, $term, $skin ) {
|
||||
return ! empty( $loop_widget_settings[ $skin . '_query_exclude' ] ) &&
|
||||
in_array( 'terms', $loop_widget_settings[ $skin . '_query_exclude' ] ) &&
|
||||
isset( $loop_widget_settings[ $skin . '_query_exclude_term_ids' ] ) &&
|
||||
in_array( $term->term_id, $loop_widget_settings[ $skin . '_query_exclude_term_ids' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function should_exclude_term_by_manual_selection( $loop_widget_settings, $term, $user_selected_taxonomy, $skin ) {
|
||||
if ( ! $this->loop_widget_has_manual_selection( $loop_widget_settings, $skin ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$terms_to_exclude_by_manual_selection = $this->get_terms_to_exclude_by_manual_selection( $loop_widget_settings[ $skin . '_query_exclude_ids' ] ?? [], $user_selected_taxonomy );
|
||||
|
||||
if ( in_array( $term->term_id, $terms_to_exclude_by_manual_selection ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
private function loop_widget_has_manual_selection( $loop_widget_settings, $skin ) {
|
||||
return ! empty( $loop_widget_settings[ $skin . '_query_exclude' ] ) &&
|
||||
in_array( 'manual_selection', $loop_widget_settings[ $skin . '_query_exclude' ] ) &&
|
||||
! empty( $loop_widget_settings[ $skin . '_query_exclude_ids' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function get_terms_to_exclude_by_manual_selection( $selected_posts, $user_selected_taxonomy ) {
|
||||
$terms_to_exclude = [];
|
||||
$term_exclude_counts = [];
|
||||
$term_actual_counts = [];
|
||||
|
||||
foreach ( $selected_posts as $post_id ) {
|
||||
$this->calculate_post_terms_counts( $post_id, $user_selected_taxonomy, $term_exclude_counts, $term_actual_counts );
|
||||
}
|
||||
|
||||
foreach ( $term_exclude_counts as $term_id => $selected_count ) {
|
||||
$this->maybe_add_term_to_exclusion( $term_id, $selected_count, $term_actual_counts, $terms_to_exclude );
|
||||
}
|
||||
|
||||
return $terms_to_exclude;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private function calculate_post_terms_counts( $post_id, $user_selected_taxonomy, &$term_exclude_counts, &$term_actual_counts ) {
|
||||
$post_terms = wp_get_post_terms( $post_id, $user_selected_taxonomy );
|
||||
|
||||
foreach ( $post_terms as $term ) {
|
||||
$this->calculate_term_counts( $term, $term_exclude_counts, $term_actual_counts );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private function calculate_term_counts( $term, &$term_exclude_counts, &$term_actual_counts ) {
|
||||
if ( empty( $term_exclude_counts[ $term->term_id ] ) ) {
|
||||
$term_exclude_counts[ $term->term_id ] = 0;
|
||||
}
|
||||
|
||||
$term_exclude_counts[ $term->term_id ] = (int) $term_exclude_counts[ $term->term_id ] + 1;
|
||||
$term_actual_counts[ $term->term_id ] = (int) $term->count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private function maybe_add_term_to_exclusion( $term_id, $selected_count, $term_actual_counts, &$terms_to_exclude ) {
|
||||
$user_selected_all_the_posts_for_this_term = $selected_count >= $term_actual_counts[ $term_id ];
|
||||
|
||||
if ( $user_selected_all_the_posts_for_this_term ) {
|
||||
$terms_to_exclude[] = $term_id;
|
||||
}
|
||||
}
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
|
||||
$this->query = Query_Constants::DATA;
|
||||
|
||||
if ( ! empty( $_SERVER['QUERY_STRING'] ) ) {
|
||||
$this->maybe_populate_filters_from_query_string();
|
||||
}
|
||||
|
||||
// Register the controller.
|
||||
new Controller();
|
||||
|
||||
add_filter( 'elementor/query/query_args', [ $this, 'filter_loop_query' ], 10, 2 );
|
||||
|
||||
add_filter( 'elementor_pro/editor/localize_settings', [ $this, 'add_localize_data' ] );
|
||||
|
||||
add_filter( 'paginate_links', [ $this, 'remove_rest_route_parameter' ] );
|
||||
|
||||
add_action( 'elementor/frontend/after_register_styles', [ $this, 'register_styles' ] );
|
||||
}
|
||||
}
|
||||
44
modules/loop-filter/query/data/query-constants.php
Normal file
44
modules/loop-filter/query/data/query-constants.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace ElementorPro\Modules\LoopFilter\Query\Data;
|
||||
|
||||
class Query_Constants {
|
||||
public const DATA = [
|
||||
'AND' => [
|
||||
'separator' => [
|
||||
'decoded' => '+',
|
||||
'from-browser' => ' ',
|
||||
'encoded' => '%2B',
|
||||
],
|
||||
'operator' => 'AND',
|
||||
'relation' => 'AND',
|
||||
],
|
||||
'OR' => [
|
||||
'separator' => [
|
||||
'decoded' => '~',
|
||||
'from-browser' => '~',
|
||||
'encoded' => '%7C',
|
||||
],
|
||||
'operator' => 'IN',
|
||||
'relation' => 'OR',
|
||||
],
|
||||
'NOT' => [
|
||||
'separator' => [
|
||||
'decoded' => '!',
|
||||
'from-browser' => '!',
|
||||
'encoded' => '%21',
|
||||
],
|
||||
'operator' => 'NOT IN',
|
||||
'relation' => 'AND',
|
||||
],
|
||||
'DISABLED' => [
|
||||
'separator' => [
|
||||
'decoded' => '',
|
||||
'from-browser' => '',
|
||||
'encoded' => '',
|
||||
],
|
||||
'operator' => 'AND',
|
||||
'relation' => 'AND',
|
||||
],
|
||||
];
|
||||
}
|
||||
10
modules/loop-filter/query/interfaces/query-interface.php
Normal file
10
modules/loop-filter/query/interfaces/query-interface.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace ElementorPro\Modules\LoopFilter\Query\Interfaces;
|
||||
|
||||
use ElementorPro\Modules\LoopFilter\Query\Taxonomy_Manager;
|
||||
|
||||
interface Query_Interface {
|
||||
public function __construct( $filter_terms, Taxonomy_Manager $taxonomy_manager );
|
||||
public function get_query();
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
namespace ElementorPro\Modules\LoopFilter\Query\QueryTypes;
|
||||
|
||||
use ElementorPro\Modules\LoopFilter\Query\Data\Query_Constants;
|
||||
use ElementorPro\Modules\LoopFilter\Query\Interfaces\Query_Interface;
|
||||
use ElementorPro\Modules\LoopFilter\Query\Taxonomy_Manager;
|
||||
|
||||
class Hierarchy_And_Query implements Query_Interface {
|
||||
private $query;
|
||||
private $terms;
|
||||
private $taxonomy;
|
||||
private $logical_join;
|
||||
private $taxonomy_manager;
|
||||
|
||||
public function __construct( $filter_terms, $taxonomy_manager ) {
|
||||
$this->query = Query_Constants::DATA;
|
||||
$this->taxonomy_manager = $taxonomy_manager;
|
||||
$this->terms = $filter_terms['hierarchical-terms'] ?? [];
|
||||
$this->taxonomy = $filter_terms['taxonomy'];
|
||||
$this->logical_join = $filter_terms['logicalJoin'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function get_query() {
|
||||
$query = [];
|
||||
|
||||
if ( empty( $this->terms ) ) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
foreach ( $this->terms as $parent_term => $terms ) {
|
||||
$filtered_terms = $this->filter_query_terms( $parent_term, $terms );
|
||||
$query[] = $this->get_hierarchy_query( $filtered_terms );
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $parent_term
|
||||
* @param $terms
|
||||
* @return array
|
||||
*/
|
||||
private function filter_query_terms( $parent_term, $terms ) {
|
||||
$query_terms = [];
|
||||
$filters_on_parent_term = in_array( $parent_term, $terms );
|
||||
$filters_on_parent_and_child_terms = count( $terms ) > 1 && $filters_on_parent_term;
|
||||
$is_parent_term_only = $filters_on_parent_term && ! $filters_on_parent_and_child_terms;
|
||||
$has_child_terms_only = ! $filters_on_parent_term;
|
||||
|
||||
// For an AND Queries exclude parent term if there are child and parent terms.
|
||||
if ( $filters_on_parent_and_child_terms ) {
|
||||
$query_terms = array_diff( $terms, [ $parent_term ] ); //drop parent term
|
||||
}
|
||||
|
||||
if ( $is_parent_term_only || $has_child_terms_only ) {
|
||||
$query_terms = $terms;
|
||||
}
|
||||
|
||||
return $query_terms;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create the Inner query for AND OR queries with one or more filter terms targeted at the same Widget.
|
||||
* @param array $terms
|
||||
* @return array
|
||||
*/
|
||||
private function get_hierarchy_query( $terms ) {
|
||||
$inner_query = [
|
||||
'taxonomy' => $this->taxonomy,
|
||||
'field' => 'slug',
|
||||
'terms' => [],
|
||||
'operator' => $this->query['OR']['operator'],
|
||||
];
|
||||
|
||||
foreach ( $terms as $term ) {
|
||||
if ( 1 < count( $terms ) && $this->taxonomy_manager->is_parent_term_with_children( $term, $this->taxonomy ) ) {
|
||||
$parent_query = $this->get_hierarchy_query( [ $term ] );
|
||||
continue;
|
||||
}
|
||||
|
||||
$inner_query['terms'] = [ urldecode( sanitize_title( $term ?? '' ) ) ]; //decode non-latin strings
|
||||
$child_queries[] = $inner_query;
|
||||
}
|
||||
|
||||
$hierarchy_query = array_merge( $parent_query ?? [], $child_queries ?? [] );
|
||||
$hierarchy_query['relation'] = $this->query['OR']['relation']; //broaden search results when two child terms are selected. And relation between unrelated terms.
|
||||
return $hierarchy_query;
|
||||
}
|
||||
}
|
||||
90
modules/loop-filter/query/query-types/hierarchy-or-query.php
Normal file
90
modules/loop-filter/query/query-types/hierarchy-or-query.php
Normal file
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
namespace ElementorPro\Modules\LoopFilter\Query\QueryTypes;
|
||||
|
||||
use ElementorPro\Modules\LoopFilter\Query\Data\Query_Constants;
|
||||
use ElementorPro\Modules\LoopFilter\Query\Interfaces\Query_Interface;
|
||||
|
||||
class Hierarchy_Or_Query implements Query_Interface {
|
||||
private $query;
|
||||
private $terms;
|
||||
private $taxonomy;
|
||||
private $logical_join;
|
||||
|
||||
public function __construct( $filter_terms, $taxonomy_manager ) {
|
||||
$this->query = Query_Constants::DATA;
|
||||
$this->terms = $filter_terms['hierarchical-terms'] ?? [];
|
||||
$this->taxonomy = $filter_terms['taxonomy'];
|
||||
$this->logical_join = $filter_terms['logicalJoin'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function get_query() {
|
||||
$query = [];
|
||||
|
||||
if ( empty( $this->terms ) ) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
foreach ( $this->terms as $parent_term => $terms ) {
|
||||
$filtered_terms = $this->filter_query_terms( $parent_term, $terms );
|
||||
$query[] = $this->get_hierarchy_query( $filtered_terms );
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description
|
||||
* @param $parent_term
|
||||
* @param $terms
|
||||
* @return array
|
||||
*/
|
||||
private function filter_query_terms( $parent_term, $terms ) {
|
||||
$query_terms = [];
|
||||
$filters_on_parent_term = in_array( $parent_term, $terms );
|
||||
$filters_on_parent_and_child_terms = count( $terms ) > 1 && $filters_on_parent_term;
|
||||
$is_parent_term_only = $filters_on_parent_term && ! $filters_on_parent_and_child_terms;
|
||||
$has_child_terms_only = ! $filters_on_parent_term;
|
||||
|
||||
// For an OR Queries exclude child terms if there is a parent term in the terms.
|
||||
if ( $filters_on_parent_and_child_terms ) {
|
||||
$query_terms = [ $parent_term ]; // drop child terms
|
||||
}
|
||||
|
||||
if ( $is_parent_term_only || $has_child_terms_only ) {
|
||||
$query_terms = $terms;
|
||||
}
|
||||
|
||||
return $query_terms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the Inner query for AND OR queries with one or more filter terms targeted at the same Widget.
|
||||
* @param array $terms
|
||||
* @return array
|
||||
*/
|
||||
private function get_hierarchy_query( $terms ) {
|
||||
$inner_query = [
|
||||
'taxonomy' => $this->taxonomy,
|
||||
'field' => 'slug',
|
||||
'terms' => [],
|
||||
'operator' => $this->query['OR']['operator'],
|
||||
];
|
||||
|
||||
foreach ( $terms as $term ) {
|
||||
if ( 1 < count( $terms ) && $this->taxonomy_manager->is_parent_term_with_children( $term, $this->taxonomy ) ) {
|
||||
$parent_query = $this->get_hierarchy_query( [ $term ] );
|
||||
continue;
|
||||
}
|
||||
|
||||
$inner_query['terms'] = [ urldecode( sanitize_title( $term ?? '' ) ) ]; //decode non-latin strings
|
||||
$child_queries[] = $inner_query;
|
||||
}
|
||||
|
||||
$hierarchy_query = array_merge( $parent_query ?? [], $child_queries ?? [] );
|
||||
$hierarchy_query['relation'] = $this->query['OR']['relation']; //broaden search results when two child terms are selected. And relation between unrelated terms.
|
||||
return $hierarchy_query;
|
||||
}
|
||||
}
|
||||
88
modules/loop-filter/query/query-types/single-terms-query.php
Normal file
88
modules/loop-filter/query/query-types/single-terms-query.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
namespace ElementorPro\Modules\LoopFilter\Query\QueryTypes;
|
||||
|
||||
use ElementorPro\Modules\LoopFilter\Query\Data\Query_Constants;
|
||||
use ElementorPro\Modules\LoopFilter\Query\Interfaces\Query_Interface;
|
||||
|
||||
class Single_Terms_Query implements Query_Interface {
|
||||
private $query;
|
||||
private $terms;
|
||||
private $taxonomy;
|
||||
private $logical_join;
|
||||
private $taxonomy_manager;
|
||||
|
||||
public function __construct( $filter_terms, $taxonomy_manager ) {
|
||||
$this->query = Query_Constants::DATA;
|
||||
$this->taxonomy_manager = $taxonomy_manager;
|
||||
$this->set_single_or_multiple_selection_terms( $filter_terms );
|
||||
$this->taxonomy = $filter_terms['taxonomy'] ?? [];
|
||||
$this->logical_join = $filter_terms['logicalJoin'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the Inner query for AND OR queries with one or more filter terms targeted at the same Widget using terms with no parent and no children
|
||||
* @return array
|
||||
*/
|
||||
public function get_query() {
|
||||
if ( empty( $this->terms ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$query = [
|
||||
[
|
||||
'taxonomy' => $this->taxonomy,
|
||||
'field' => 'slug',
|
||||
'terms' => [],
|
||||
'operator' => $this->get_inner_query_operator(),
|
||||
],
|
||||
];
|
||||
|
||||
foreach ( $this->terms as $term ) {
|
||||
$query[0]['terms'][] = urldecode( sanitize_title( $term ?? '' ) ); //decode non-latin strings
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $filter_terms
|
||||
* @return void
|
||||
*/
|
||||
private function set_single_or_multiple_selection_terms( $filter_terms ) {
|
||||
// Single Selection
|
||||
if ( ! empty( $filter_terms['single-term'][0] ) ) {
|
||||
$this->terms = $filter_terms['single-term'];
|
||||
return;
|
||||
}
|
||||
|
||||
$this->terms = $filter_terms['parent-terms-without-children'];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string 'IN' / 'AND' (default)
|
||||
*/
|
||||
private function get_inner_query_operator() {
|
||||
if ( $this->is_single_parent_term() ) {
|
||||
return $this->query['OR']['operator']; // Allow showing posts from parent category when it's selected.
|
||||
}
|
||||
|
||||
if ( 'DISABLED' !== $this->logical_join ) {
|
||||
return $this->query[ $this->logical_join ?? 'DISABLED' ]['operator'];
|
||||
}
|
||||
|
||||
return $this->query['AND']['operator'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
private function is_single_parent_term() {
|
||||
if ( empty( $this->terms ?? [] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$is_parent_term = $this->taxonomy_manager->is_parent_term_with_children( $this->terms[0], $this->taxonomy );
|
||||
return 1 === count( $this->terms ?? [] ) && $is_parent_term;
|
||||
}
|
||||
}
|
||||
260
modules/loop-filter/query/taxonomy-manager.php
Normal file
260
modules/loop-filter/query/taxonomy-manager.php
Normal file
@@ -0,0 +1,260 @@
|
||||
<?php
|
||||
namespace ElementorPro\Modules\LoopFilter\Query;
|
||||
|
||||
class Taxonomy_Manager {
|
||||
private $terms_by_slug = [];
|
||||
private $terms_by_id = [];
|
||||
private $terms_hierarchy = [];
|
||||
|
||||
/**
|
||||
* @param string $taxonomy default 'category'. Use taxonomy string i.e. 'product_cat'. This generates the terms_by_slug and terms_by_id arrays.
|
||||
* @return void
|
||||
*/
|
||||
public function get_taxonomy_terms( $taxonomy = 'category' ) {
|
||||
$args = [
|
||||
'taxonomy' => $taxonomy,
|
||||
'hide_empty' => true,
|
||||
];
|
||||
|
||||
$terms = get_terms( $args );
|
||||
|
||||
if ( is_wp_error( $terms ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->get_terms_by_slug_and_id( $terms );
|
||||
$this->terms_hierarchy = $this->filter_child_terms_by_depth( $terms, 100 );
|
||||
}
|
||||
|
||||
private function get_term_id( $slug, $taxonomy ) {
|
||||
if ( ! isset( $this->terms_by_slug[ $taxonomy ][ $slug ] ) ) {
|
||||
return -1;
|
||||
}
|
||||
return $this->terms_by_slug[ $taxonomy ][ $slug ]['term_id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a term is a parent term.
|
||||
* @param string $slug
|
||||
* @param string $taxonomy
|
||||
* @return bool;
|
||||
*/
|
||||
public function is_parent_term_without_children( $slug, $taxonomy ) {
|
||||
// Empty terms do not exist in $terms_by_slug
|
||||
$has_children = $this->has_children( $slug, $taxonomy );
|
||||
$is_top_level_parent_term = $this->is_top_level_parent_term( $slug, $taxonomy );
|
||||
return ( isset( $this->terms_by_slug[ $taxonomy ][ $slug ] ) && $is_top_level_parent_term && ! $has_children );
|
||||
}
|
||||
|
||||
public function is_parent_term_with_children( $slug, $taxonomy ) {
|
||||
// Empty terms do not exist in $terms_by_slug
|
||||
return ( isset( $this->terms_by_slug[ $taxonomy ][ $slug ] ) && $this->has_children( $slug, $taxonomy ) );
|
||||
}
|
||||
|
||||
private function has_children( $slug, $taxonomy ) {
|
||||
$term_id = $this->get_term_id( $slug, $taxonomy );
|
||||
return isset( $this->terms_hierarchy[ $term_id ] ) && count( $this->terms_hierarchy[ $term_id ] ) > 0;
|
||||
}
|
||||
|
||||
private function get_children( $slug, $taxonomy ) {
|
||||
$term_id = $this->get_term_id( $slug, $taxonomy );
|
||||
return $this->terms_hierarchy[ $term_id ];
|
||||
}
|
||||
|
||||
private function is_child_term( $slug, $taxonomy ) {
|
||||
return ( isset( $this->terms_by_slug[ $taxonomy ][ $slug ] ) && 0 !== $this->terms_by_slug[ $taxonomy ][ $slug ]['parent'] );
|
||||
}
|
||||
|
||||
public function is_top_level_parent_term( $slug, $taxonomy ) {
|
||||
return ( isset( $this->terms_by_slug[ $taxonomy ][ $slug ] ) && 0 === $this->terms_by_slug[ $taxonomy ][ $slug ]['parent'] );
|
||||
}
|
||||
|
||||
private function get_parent( $slug, $taxonomy ) {
|
||||
return $this->terms_by_slug[ $taxonomy ][ $slug ]['parent'];
|
||||
}
|
||||
|
||||
private function get_parent_id( $child_slug, $taxonomy ) {
|
||||
if ( ! isset( $this->terms_by_slug[ $taxonomy ][ $child_slug ]['parent'] ) ) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return $this->terms_by_slug[ $taxonomy ][ $child_slug ]['parent'];
|
||||
}
|
||||
|
||||
private function get_parent_slug( $child_slug, $taxonomy ) {
|
||||
$parent_id = $this->get_parent_id( $child_slug, $taxonomy );
|
||||
|
||||
if ( -1 === $parent_id ) {
|
||||
return 'UNDEFINED';
|
||||
}
|
||||
|
||||
return $this->terms_by_id[ $taxonomy ][ $parent_id ]['slug'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $filter_terms
|
||||
* @param string $taxonomy
|
||||
* @return array
|
||||
*/
|
||||
public function get_hierarchy_of_selected_terms( $filter_terms, $taxonomy ) {
|
||||
// Taxonomy Filter parameter is empty i.e. `e-filter-389c132-product_cat=`.
|
||||
if ( ! empty( $filter_terms['terms'] ) && '' === $filter_terms['terms'][0] ) {
|
||||
return [
|
||||
'single-term' => [],
|
||||
'parent-terms-without-children' => [],
|
||||
'hierarchical-terms' => [],
|
||||
'logicalJoin' => $filter_terms['logicalJoin'],
|
||||
'taxonomy' => $taxonomy,
|
||||
];
|
||||
}
|
||||
|
||||
$parents_without_children = [];
|
||||
$parents_with_children_by_parent = [];
|
||||
$single_selection_term = [];
|
||||
|
||||
if ( 1 === count( $filter_terms['terms'] ) ) {
|
||||
$single_selection_term = $filter_terms['terms'];
|
||||
}
|
||||
|
||||
foreach ( $filter_terms['terms'] as $term ) {
|
||||
|
||||
if ( ! empty( $single_selection_term ) ) {
|
||||
break;
|
||||
}
|
||||
|
||||
$term = urldecode( sanitize_title( $term ) ); // decode non-latin slugs.
|
||||
$is_parent_term_without_children = $this->is_parent_term_without_children( $term, $taxonomy );
|
||||
$is_parent_term_with_children = $this->is_parent_term_with_children( $term, $taxonomy );
|
||||
|
||||
if ( $is_parent_term_without_children ) {
|
||||
$parents_without_children[] = $term;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $is_parent_term_with_children ) {
|
||||
$parents_with_children_by_parent[ $term ][] = $term;
|
||||
continue;
|
||||
}
|
||||
|
||||
$parent_slug = $this->get_parent_slug( $term, $taxonomy );
|
||||
if ( 'UNDEFINED' === $parent_slug ) {
|
||||
continue;
|
||||
}
|
||||
$parents_with_children_by_parent[ $parent_slug ][] = $term;
|
||||
}
|
||||
|
||||
return [
|
||||
'single-term' => $single_selection_term,
|
||||
'parent-terms-without-children' => $parents_without_children,
|
||||
'hierarchical-terms' => $parents_with_children_by_parent,
|
||||
'logicalJoin' => $filter_terms['logicalJoin'],
|
||||
'taxonomy' => $taxonomy,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $terms
|
||||
* @return void
|
||||
*/
|
||||
private function get_terms_by_slug_and_id( $terms = [] ) {
|
||||
$this->terms_by_slug = [];
|
||||
foreach ( $terms as $term ) {
|
||||
$slug = urldecode( $term->slug );
|
||||
|
||||
$this->try_set_terms_by_slug( $slug, $term );
|
||||
$this->try_set_terms_by_id( $slug, $term );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $slug
|
||||
* @param string $term
|
||||
* @return void
|
||||
*/
|
||||
public function try_set_terms_by_slug( $slug, $term ) {
|
||||
$term_id = $term->term_id;
|
||||
$taxonomy = $term->taxonomy;
|
||||
|
||||
if ( ! isset( $this->terms_by_slug[ $taxonomy ][ $slug ] ) ) {
|
||||
$this->terms_by_slug[ $taxonomy ][ $slug ] = [
|
||||
'term_id' => $term_id,
|
||||
'parent' => $term->parent,
|
||||
'count' => $term->count,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $slug
|
||||
* @param string $term
|
||||
* @return void
|
||||
*/
|
||||
public function try_set_terms_by_id( $slug, $term ) {
|
||||
$term_id = $term->term_id;
|
||||
$taxonomy = $term->taxonomy;
|
||||
|
||||
if ( ! isset( $this->terms_by_id[ $taxonomy ][ $slug ] ) ) {
|
||||
$this->terms_by_id[ $taxonomy ][ $term_id ] = [
|
||||
'slug' => $slug,
|
||||
'parent' => $term->parent,
|
||||
'count' => $term->count,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WP_Term[] $terms
|
||||
* @param int $target_depth
|
||||
* @return \WP_Term[]
|
||||
*/
|
||||
private function filter_child_terms_by_depth( $terms, $target_depth ) {
|
||||
$filtered = [];
|
||||
|
||||
foreach ( $terms as $term ) {
|
||||
$this->filter_single_term( $filtered, $terms, $term, $target_depth );
|
||||
}
|
||||
|
||||
return $filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WP_Term[] $terms
|
||||
* @param \WP_Term $current_term
|
||||
* @param int $target_depth
|
||||
* @return void
|
||||
*/
|
||||
private function filter_single_term( &$result, $terms, $current_term, $target_depth ) {
|
||||
if ( 0 === $current_term->parent ) {
|
||||
$result[ $current_term->parent ][ $current_term->term_id ] = $current_term;
|
||||
return;
|
||||
}
|
||||
|
||||
$item_depth = $this->calculate_depth_for_child_term_recursively( $terms, $current_term, 0 );
|
||||
|
||||
if ( $item_depth <= $target_depth ) {
|
||||
$result[ $current_term->parent ][ $current_term->term_id ] = $current_term;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WP_Term[] $terms
|
||||
* @param \WP_Term $child_term
|
||||
* @param int $depth
|
||||
* @return int|void
|
||||
*/
|
||||
private function calculate_depth_for_child_term_recursively( $terms, $child_term, $depth ) {
|
||||
$depth++;
|
||||
|
||||
foreach ( $terms as $term ) {
|
||||
if ( $term->term_id !== $child_term->parent ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( 0 === $term->parent ) {
|
||||
return $depth;
|
||||
}
|
||||
return $this->calculate_depth_for_child_term_recursively( $terms, $term, $depth );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
81
modules/loop-filter/query/taxonomy-query-builder.php
Normal file
81
modules/loop-filter/query/taxonomy-query-builder.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace ElementorPro\Modules\LoopFilter\Query;
|
||||
|
||||
use ElementorPro\Modules\LoopFilter\Query\Data\Query_Constants;
|
||||
use ElementorPro\Modules\LoopFilter\Query\Interfaces\Query_Interface;
|
||||
use ElementorPro\Modules\LoopFilter\Query\QueryTypes\Hierarchy_And_Query;
|
||||
use ElementorPro\Modules\LoopFilter\Query\QueryTypes\Hierarchy_Or_Query;
|
||||
use ElementorPro\Modules\LoopFilter\Query\QueryTypes\Single_Terms_Query;
|
||||
|
||||
class Taxonomy_Query_Builder {
|
||||
private $query;
|
||||
private $single_terms_query;
|
||||
private $hierarchy_query;
|
||||
private $filter_terms;
|
||||
private $tax_query;
|
||||
private $taxonomy_manager;
|
||||
|
||||
public function __construct() {
|
||||
$this->query = Query_Constants::DATA;
|
||||
}
|
||||
|
||||
public function get_merged_queries( &$tax_query, $taxonomy, $filter ) {
|
||||
$this->tax_query = &$tax_query;
|
||||
|
||||
// Taxonomy Filter parameter is empty i.e. `e-filter-389c132-product_cat=`.
|
||||
if ( ! empty( $filter['terms'] ) && '' === $filter['terms'][0] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->taxonomy_manager = new Taxonomy_Manager();
|
||||
$this->taxonomy_manager->get_taxonomy_terms( $taxonomy );
|
||||
$this->filter_terms = $this->taxonomy_manager->get_hierarchy_of_selected_terms( $filter, $taxonomy );
|
||||
|
||||
if ( ! empty( $this->filter_terms['single-term'] ) ) {
|
||||
$this->get_single_selection_query( $tax_query );
|
||||
return;
|
||||
}
|
||||
|
||||
$this->get_multiple_selection_query( $tax_query );
|
||||
}
|
||||
|
||||
private function get_single_selection_query( &$tax_query ) {
|
||||
$this->single_terms_query = $this->get_query( new Single_Terms_Query( $this->filter_terms, $this->taxonomy_manager ) );
|
||||
$this->merge_single_selection_query( $tax_query );
|
||||
}
|
||||
|
||||
private function get_multiple_selection_query( &$tax_query ) {
|
||||
$this->single_terms_query = $this->get_query( new Single_Terms_Query( $this->filter_terms, $this->taxonomy_manager ) );
|
||||
|
||||
if ( 'AND' === $this->filter_terms['logicalJoin'] ) {
|
||||
$this->hierarchy_query = $this->get_query( new Hierarchy_And_Query( $this->filter_terms, $this->taxonomy_manager ) );
|
||||
}
|
||||
|
||||
if ( 'OR' === $this->filter_terms['logicalJoin'] ) {
|
||||
$this->hierarchy_query = $this->get_query( new Hierarchy_Or_Query( $this->filter_terms, $this->taxonomy_manager ) );
|
||||
}
|
||||
|
||||
$this->merge_multiple_selection_query( $tax_query );
|
||||
}
|
||||
|
||||
private function merge_single_selection_query( &$tax_query ) {
|
||||
if ( empty( $this->single_terms_query ?? [] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tax_query = array_merge( $tax_query, $this->single_terms_query ?? [] );
|
||||
}
|
||||
|
||||
private function merge_multiple_selection_query( &$tax_query ) {
|
||||
if ( empty( $this->single_terms_query ?? [] ) && empty( $this->hierarchy_query ?? [] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tax_query = [ array_merge( $tax_query, $this->single_terms_query ?? [], $this->hierarchy_query ?? [], [ 'relation' => $this->query[ $this->filter_terms['logicalJoin'] ]['relation'] ] ) ];
|
||||
}
|
||||
|
||||
private function get_query( Query_Interface $query_type ) {
|
||||
return $query_type->get_query();
|
||||
}
|
||||
}
|
||||
80
modules/loop-filter/traits/hierarchical-taxonomy-trait.php
Normal file
80
modules/loop-filter/traits/hierarchical-taxonomy-trait.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
namespace ElementorPro\Modules\LoopFilter\Traits;
|
||||
|
||||
trait Hierarchical_Taxonomy_Trait {
|
||||
|
||||
/**
|
||||
* @param \WP_Term[] $terms
|
||||
* @param int $target_depth
|
||||
* @return \WP_Term[]
|
||||
*/
|
||||
public function filter_child_terms_by_depth( $terms, $target_depth ) {
|
||||
$filtered = [];
|
||||
|
||||
foreach ( $terms as $term ) {
|
||||
$this->filter_single_term( $filtered, $terms, $term, $target_depth );
|
||||
}
|
||||
|
||||
return $filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WP_Term[] $terms
|
||||
* @param \WP_Term $current_term
|
||||
* @param int $target_depth
|
||||
* @return void
|
||||
*/
|
||||
private function filter_single_term( &$result, $terms, $current_term, $target_depth ) {
|
||||
if ( 0 === $current_term->parent ) {
|
||||
$result[ $current_term->parent ][] = $current_term;
|
||||
return;
|
||||
}
|
||||
|
||||
$item_depth = $this->calculate_depth_for_child_term( $terms, $current_term, 0 );
|
||||
|
||||
if ( $item_depth <= $target_depth ) {
|
||||
$result[ $current_term->parent ][] = $current_term;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WP_Term[] $terms
|
||||
* @param \WP_Term $child_term
|
||||
* @param int $depth
|
||||
* @return int|void
|
||||
*/
|
||||
private function calculate_depth_for_child_term( $terms, $child_term, $depth ) {
|
||||
$depth++;
|
||||
|
||||
foreach ( $terms as $term ) {
|
||||
if ( $term->term_id !== $child_term->parent ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( 0 === $term->parent ) {
|
||||
return $depth;
|
||||
}
|
||||
return $this->calculate_depth_for_child_term( $terms, $term, $depth );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Transform terms hierarchy structure to plain [ parent_term_id => [ term, term ... ], ...] to [ term, term, ... ]
|
||||
*
|
||||
* @param array $taxonomy_plain_view
|
||||
* @param array $hierarchy_terms
|
||||
* @param int $parent_term_id
|
||||
* @return void
|
||||
*/
|
||||
public function transform_taxonomy_hierarchy_to_plain( &$taxonomy_plain_view, $hierarchy_terms, $parent_term_id = 0 ) {
|
||||
if ( empty( $hierarchy_terms[ $parent_term_id ] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $hierarchy_terms[ $parent_term_id ] as $term ) {
|
||||
$taxonomy_plain_view[] = $term;
|
||||
$this->transform_taxonomy_hierarchy_to_plain( $taxonomy_plain_view, $hierarchy_terms, $term->term_id );
|
||||
}
|
||||
}
|
||||
}
|
||||
202
modules/loop-filter/traits/taxonomy-filter-trait.php
Normal file
202
modules/loop-filter/traits/taxonomy-filter-trait.php
Normal file
@@ -0,0 +1,202 @@
|
||||
<?php
|
||||
|
||||
namespace ElementorPro\Modules\LoopFilter\Traits;
|
||||
|
||||
use WP_Query;
|
||||
use ElementorPro\Core\Utils;
|
||||
use Elementor\Plugin;
|
||||
use ElementorPro\Modules\QueryControl\Module as QueryControl;
|
||||
|
||||
trait Taxonomy_Filter_Trait {
|
||||
use Hierarchical_Taxonomy_Trait;
|
||||
|
||||
protected function get_taxonomy_options( array $post_types, $key_prefix = '' ) {
|
||||
$post_type_taxonomies = [];
|
||||
$taxonomies_to_exclude = [];
|
||||
|
||||
foreach ( $post_types as $post_type ) {
|
||||
$post_type_taxonomies = array_merge( $post_type_taxonomies, get_object_taxonomies( $post_type, 'object' ) );
|
||||
$taxonomies_to_exclude = array_merge( $taxonomies_to_exclude, $this->get_taxonomies_to_exclude( $post_type ) );
|
||||
}
|
||||
|
||||
$control_options = [];
|
||||
|
||||
foreach ( $post_type_taxonomies as $taxonomy ) {
|
||||
if ( $this->should_exclude_taxonomy( $taxonomy->name, $taxonomies_to_exclude ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$control_options[ $key_prefix . $taxonomy->name ] = $taxonomy->label;
|
||||
}
|
||||
|
||||
return $control_options;
|
||||
}
|
||||
|
||||
private function get_loop_widget_data( $document, $selected_element = null ) {
|
||||
$elements_data = $document->get_elements_data();
|
||||
|
||||
return \Elementor\Utils::find_element_recursive(
|
||||
$elements_data,
|
||||
$selected_element,
|
||||
);
|
||||
}
|
||||
|
||||
private function get_loop_widget( $selected_element ) {
|
||||
$post_id = Utils::get_current_post_id();
|
||||
|
||||
if ( ! $post_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$document = Plugin::$instance->documents->get_doc_for_frontend( $post_id );
|
||||
$widget_data = $this->get_loop_widget_data( $document, $selected_element );
|
||||
|
||||
if ( ! $widget_data ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Plugin::$instance->elements_manager->create_element_instance( $widget_data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts Elementor query arguments to prevent taxonomy filter conflicts.
|
||||
*
|
||||
* Resolves an issue where the taxonomy filter unintentionally affects itself
|
||||
* when a Loop Grid widget is filtered by taxonomy. Without this adjustment,
|
||||
* taxonomy terms added by the filter itself may be included in the query results,
|
||||
* leading to unexpected behavior.
|
||||
*
|
||||
* This function ensures that only taxonomy terms specified via the query control
|
||||
* (with the `term_taxonomy_id` field) are considered in the `tax_query`,
|
||||
* excluding any others introduced by the filter.
|
||||
*
|
||||
* @param array $query_args The query arguments for the Elementor widget.
|
||||
* @param object $widget The Elementor widget instance.
|
||||
* @return array The modified query arguments.
|
||||
*/
|
||||
public function modify_elementor_query_args( $query_args, $widget ) {
|
||||
global $wp_query;
|
||||
|
||||
if ( isset( $query_args['tax_query'] ) && ! empty( $wp_query->include_field_ids_arg ) ) {
|
||||
$query_args['tax_query'] = array_filter(
|
||||
$query_args['tax_query'],
|
||||
function( $tax ) {
|
||||
return isset( $tax['field'] ) && 'term_taxonomy_id' === $tax['field'];
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return $query_args;
|
||||
}
|
||||
|
||||
private function get_elementor_post_query( $loop_widget ): WP_Query {
|
||||
global $wp_query;
|
||||
|
||||
$wp_query->include_field_ids_arg = true;
|
||||
$query_args = [
|
||||
'posts_per_page' => -1,
|
||||
'fields' => 'ids',
|
||||
];
|
||||
|
||||
add_filter( 'elementor/query/query_args', [ $this, 'modify_elementor_query_args' ], 15, 2 );
|
||||
|
||||
$query = QueryControl::instance()->get_query_ignoring_avoid_list( $loop_widget, $loop_widget->get_query_name(), $query_args );
|
||||
|
||||
remove_filter( 'elementor/query/query_args', [ $this, 'modify_elementor_query_args' ] );
|
||||
|
||||
unset( $wp_query->include_field_ids_arg );
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $settings
|
||||
* @return bool
|
||||
*/
|
||||
public function should_exclude_child_taxonomies( array $settings ): bool {
|
||||
return ! isset( $settings['show_child_taxonomy'] ) || 'yes' !== $settings['show_child_taxonomy'];
|
||||
}
|
||||
|
||||
public function should_hide_empty_items( array $settings ): bool {
|
||||
return 'yes' !== $settings['show_empty_items'];
|
||||
}
|
||||
|
||||
private function maybe_add_filtered_post_ids_to_args( array $args, $loop_widget, array $settings ): array {
|
||||
if ( $loop_widget && $this->should_hide_empty_items( $settings ) ) {
|
||||
$post_query = $this->get_elementor_post_query( $loop_widget );
|
||||
$post_ids = $post_query->posts;
|
||||
|
||||
$args['object_ids'] = $post_ids;
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
/**
|
||||
* @param array $settings
|
||||
* @param array $display_settings
|
||||
* @return void|\WP_Term[]
|
||||
*/
|
||||
public function get_filtered_taxonomies( $settings, $display_settings ) {
|
||||
$args = [
|
||||
'taxonomy' => $settings['taxonomy'],
|
||||
'hide_empty' => $this->should_hide_empty_items( $settings ),
|
||||
];
|
||||
$avoid_reset_parent = ! empty( $settings['avoid_reset_parent'] );
|
||||
|
||||
if ( $this->should_exclude_child_taxonomies( $settings ) && ! $avoid_reset_parent ) {
|
||||
$args['parent'] = 0;
|
||||
}
|
||||
|
||||
if ( isset( $settings['selected_element'] ) && ! wp_doing_ajax() ) {
|
||||
$loop_widget = $this->get_loop_widget( $settings['selected_element'] );
|
||||
$args = $this->maybe_add_filtered_post_ids_to_args( $args, $loop_widget, $settings );
|
||||
}
|
||||
|
||||
$args = apply_filters(
|
||||
'elementor/loop_taxonomy/args',
|
||||
$this->get_additional_allowed_args( $args, $display_settings ),
|
||||
$settings,
|
||||
$display_settings,
|
||||
);
|
||||
|
||||
$terms = get_terms( $args );
|
||||
|
||||
if ( is_wp_error( $terms ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( isset( $settings['show_child_taxonomy'] ) && 'yes' === $settings['show_child_taxonomy'] && isset( $display_settings['child_taxonomy_depth'] ) ) {
|
||||
$taxonomy_plain_view = [];
|
||||
$hierarchy_terms = $this->filter_child_terms_by_depth( $terms, $display_settings['child_taxonomy_depth'] );
|
||||
$this->transform_taxonomy_hierarchy_to_plain( $taxonomy_plain_view, $hierarchy_terms );
|
||||
$terms = ! empty( $taxonomy_plain_view ) ? $taxonomy_plain_view : $terms;
|
||||
}
|
||||
|
||||
return $terms;
|
||||
}
|
||||
|
||||
private function get_additional_allowed_args( $args, $display_settings ) {
|
||||
$allowed_args = [ 'include', 'exclude', 'term_taxonomy_id', 'child_taxonomy_depth', 'hierarchical', 'child_of', 'offset', 'orderby', 'order', 'number' ];
|
||||
|
||||
foreach ( $allowed_args as $arg ) {
|
||||
if ( isset( $display_settings[ $arg ] ) ) {
|
||||
$args[ $arg ] = $display_settings[ $arg ];
|
||||
}
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
private function get_taxonomies_to_exclude( $post_type ) {
|
||||
$excluded_taxonomies_per_post_type = [
|
||||
'post' => [ 'post_format' ],
|
||||
'product' => [ 'product_type', 'product_visibility', 'product_shipping_class', 'pa_color', 'pa_size' ],
|
||||
];
|
||||
|
||||
return $excluded_taxonomies_per_post_type[ $post_type ] ?? [];
|
||||
}
|
||||
|
||||
private function should_exclude_taxonomy( $taxonomy_name, $taxonomies_to_exclude ) {
|
||||
return ! empty( $taxonomies_to_exclude ) && in_array( $taxonomy_name, $taxonomies_to_exclude );
|
||||
}
|
||||
}
|
||||
828
modules/loop-filter/widgets/taxonomy-filter.php
Normal file
828
modules/loop-filter/widgets/taxonomy-filter.php
Normal file
@@ -0,0 +1,828 @@
|
||||
<?php
|
||||
namespace ElementorPro\Modules\LoopFilter\Widgets;
|
||||
|
||||
use Elementor\Controls_Manager;
|
||||
use Elementor\Group_Control_Background;
|
||||
use Elementor\Group_Control_Border;
|
||||
use Elementor\Group_Control_Box_Shadow;
|
||||
use Elementor\Group_Control_Text_Shadow;
|
||||
use Elementor\Group_Control_Typography;
|
||||
use ElementorPro\Base\Base_Widget;
|
||||
use ElementorPro\Modules\LoopFilter\Traits\Hierarchical_Taxonomy_Trait;
|
||||
use ElementorPro\Modules\LoopFilter\Traits\Taxonomy_Filter_Trait;
|
||||
use ElementorPro\Plugin;
|
||||
use Elementor\Utils;
|
||||
use ElementorPro\Modules\ThemeBuilder\Module as ThemeBuilderModule;
|
||||
use ElementorPro\Modules\Posts\Traits\Pagination_Trait;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Taxonomy_Filter extends Base_Widget {
|
||||
use Hierarchical_Taxonomy_Trait;
|
||||
use Taxonomy_Filter_Trait;
|
||||
use Pagination_Trait;
|
||||
|
||||
public function get_name() {
|
||||
return 'taxonomy-filter';
|
||||
}
|
||||
|
||||
public function get_group_name() {
|
||||
return 'loop-filter';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return esc_html__( 'Taxonomy Filter', 'elementor-pro' );
|
||||
}
|
||||
|
||||
public function get_icon() {
|
||||
return 'eicon-taxonomy-filter';
|
||||
}
|
||||
|
||||
public function get_keywords() {
|
||||
return [ 'filter', 'loop', 'filter bar', 'taxonomy', 'categories', 'tags' ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get style dependencies.
|
||||
*
|
||||
* Retrieve the list of style dependencies the widget requires.
|
||||
*
|
||||
* @since 3.24.0
|
||||
* @access public
|
||||
*
|
||||
* @return array Widget style dependencies.
|
||||
*/
|
||||
public function get_style_depends(): array {
|
||||
return [ 'widget-loop-filter' ];
|
||||
}
|
||||
|
||||
public function has_widget_inner_wrapper(): bool {
|
||||
return ! Plugin::elementor()->experiments->is_feature_active( 'e_optimized_markup' );
|
||||
}
|
||||
|
||||
protected function register_controls() {
|
||||
$this->start_controls_section(
|
||||
'section_taxonomy_filter',
|
||||
[
|
||||
'label' => esc_html__( 'Layout', 'elementor-pro' ),
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'selected_element',
|
||||
[
|
||||
'label' => esc_html__( 'Selected loop grid', 'elementor-pro' ),
|
||||
'type' => Controls_Manager::SELECT,
|
||||
'options' => [
|
||||
'' => esc_html__( 'Select a widget', 'elementor-pro' ),
|
||||
],
|
||||
'label_block' => true,
|
||||
'frontend_available' => true,
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'taxonomy',
|
||||
[
|
||||
'label' => esc_html__( 'Taxonomy', 'elementor-pro' ),
|
||||
'type' => Controls_Manager::SELECT,
|
||||
'options' => [
|
||||
'' => esc_html__( 'Select a taxonomy', 'elementor-pro' ),
|
||||
],
|
||||
'label_block' => true,
|
||||
'condition' => [
|
||||
'selected_element!' => '',
|
||||
],
|
||||
'frontend_available' => true,
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_responsive_control(
|
||||
'direction',
|
||||
[
|
||||
'label' => esc_html__( 'Direction', 'elementor-pro' ),
|
||||
'type' => Controls_Manager::SELECT,
|
||||
'options' => [
|
||||
'horizontal' => esc_html__( 'Horizontal', 'elementor-pro' ),
|
||||
'vertical' => esc_html__( 'Vertical', 'elementor-pro' ),
|
||||
],
|
||||
'default' => 'horizontal',
|
||||
'selectors_dictionary' => [
|
||||
'horizontal' => '--e-filter-direction: row;--e-filter-white-space: nowrap;',
|
||||
'vertical' => '--e-filter-direction: column;--e-filter-white-space: initial;',
|
||||
],
|
||||
'selectors' => [
|
||||
'{{WRAPPER}}' => '{{VALUE}};',
|
||||
],
|
||||
'condition' => [
|
||||
'selected_element!' => '',
|
||||
],
|
||||
'control_type' => 'content',
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_responsive_control(
|
||||
'item_alignment_horizontal',
|
||||
[
|
||||
'label' => esc_html__( 'Item Alignment', 'elementor-pro' ),
|
||||
'type' => Controls_Manager::CHOOSE,
|
||||
'options' => [
|
||||
'start' => [
|
||||
'title' => esc_html__( 'Start', 'elementor-pro' ),
|
||||
'icon' => 'eicon-align-start-h',
|
||||
],
|
||||
'center' => [
|
||||
'title' => esc_html__( 'Center', 'elementor-pro' ),
|
||||
'icon' => 'eicon-align-center-h',
|
||||
],
|
||||
'end' => [
|
||||
'title' => esc_html__( 'End', 'elementor-pro' ),
|
||||
'icon' => 'eicon-align-end-h',
|
||||
],
|
||||
'stretch' => [
|
||||
'title' => esc_html__( 'Stretch', 'elementor-pro' ),
|
||||
'icon' => 'eicon-align-stretch-h',
|
||||
],
|
||||
],
|
||||
'selectors' => [
|
||||
'{{WRAPPER}}' => '{{VALUE}};',
|
||||
],
|
||||
'selectors_dictionary' => [
|
||||
'start' => '--e-filter-justify-content: flex-start; --e-filter-item-width: initial; --e-filter-item-flex-grow: 0;',
|
||||
'center' => '--e-filter-justify-content: center; --e-filter-item-width: initial; --e-filter-item-flex-grow: 0;',
|
||||
'end' => '--e-filter-justify-content: flex-end; --e-filter-item-width: initial; --e-filter-item-flex-grow: 0;',
|
||||
'stretch' => '--e-filter-justify-content: initial; --e-filter-item-width: 100%; --e-filter-item-flex-grow: 1;',
|
||||
],
|
||||
'condition' => [
|
||||
'direction' => 'horizontal',
|
||||
'selected_element!' => '',
|
||||
],
|
||||
'frontend_available' => true,
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_responsive_control(
|
||||
'title_alignment_horizontal',
|
||||
[
|
||||
'label' => esc_html__( 'Title Alignment', 'elementor-pro' ),
|
||||
'type' => Controls_Manager::CHOOSE,
|
||||
'options' => [
|
||||
'start' => [
|
||||
'title' => esc_html__( 'Start', 'elementor-pro' ),
|
||||
'icon' => 'eicon-align-start-h',
|
||||
],
|
||||
'center' => [
|
||||
'title' => esc_html__( 'Center', 'elementor-pro' ),
|
||||
'icon' => 'eicon-align-center-h',
|
||||
],
|
||||
'end' => [
|
||||
'title' => esc_html__( 'End', 'elementor-pro' ),
|
||||
'icon' => 'eicon-align-end-h',
|
||||
],
|
||||
],
|
||||
'selectors' => [
|
||||
'{{WRAPPER}}' => '{{VALUE}};',
|
||||
],
|
||||
'selectors_dictionary' => [
|
||||
'start' => '--e-filter-item-justify-content: flex-start;',
|
||||
'center' => '--e-filter-item-justify-content: center;',
|
||||
'end' => '--e-filter-item-justify-content: flex-end;',
|
||||
],
|
||||
'condition' => [
|
||||
'direction' => 'horizontal',
|
||||
'selected_element!' => '',
|
||||
'item_alignment_horizontal' => 'stretch',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_responsive_control(
|
||||
'item_alignment_vertical',
|
||||
[
|
||||
'label' => esc_html__( 'Item Alignment', 'elementor-pro' ),
|
||||
'type' => Controls_Manager::CHOOSE,
|
||||
'options' => [
|
||||
'start' => [
|
||||
'title' => esc_html__( 'Start', 'elementor-pro' ),
|
||||
'icon' => 'eicon-align-start-h',
|
||||
],
|
||||
'center' => [
|
||||
'title' => esc_html__( 'Center', 'elementor-pro' ),
|
||||
'icon' => 'eicon-align-center-h',
|
||||
],
|
||||
'end' => [
|
||||
'title' => esc_html__( 'End', 'elementor-pro' ),
|
||||
'icon' => 'eicon-align-end-h',
|
||||
],
|
||||
'stretch' => [
|
||||
'title' => esc_html__( 'Stretch', 'elementor-pro' ),
|
||||
'icon' => 'eicon-align-stretch-h',
|
||||
],
|
||||
],
|
||||
'selectors' => [
|
||||
'{{WRAPPER}}' => '{{VALUE}};',
|
||||
],
|
||||
'selectors_dictionary' => [
|
||||
'start' => '--e-filter-align-items: flex-start; --e-filter-item-width: initial; --e-filter-item-max-width: calc(100% - calc(var( --e-filter-item-padding )*3 ) ); --e-filter-item-flex-grow: initial; --e-filter-item-box-sizing: initial; --e-filter-item-align-text: start;',
|
||||
'center' => '--e-filter-align-items: center; --e-filter-item-width: initial; --e-filter-item-max-width: calc(100% - calc(var( --e-filter-item-padding )*3 ) ); --e-filter-item-flex-grow: initial; --e-filter-item-box-sizing: initial; --e-filter-item-align-text: center;',
|
||||
'end' => '--e-filter-align-items: flex-end; --e-filter-item-width: initial; --e-filter-item-max-width: calc(100% - calc(var( --e-filter-item-padding )*3 ) ); --e-filter-item-flex-grow: initial; --e-filter-item-box-sizing: initial; --e-filter-item-align-text: end;',
|
||||
'stretch' => '--e-filter-align-items: center; --e-filter-item-width: 100%; --e-filter-item-max-width: 100%; --e-filter-item-flex-grow: 1; --e-filter-item-box-sizing: border-box; --e-filter-item-align-text: center;',
|
||||
],
|
||||
'condition' => [
|
||||
'direction' => 'vertical',
|
||||
'selected_element!' => '',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_responsive_control(
|
||||
'title_alignment_vertical',
|
||||
[
|
||||
'label' => esc_html__( 'Title Alignment', 'elementor-pro' ),
|
||||
'type' => Controls_Manager::CHOOSE,
|
||||
'options' => [
|
||||
'start' => [
|
||||
'title' => esc_html__( 'Start', 'elementor-pro' ),
|
||||
'icon' => 'eicon-align-start-h',
|
||||
],
|
||||
'center' => [
|
||||
'title' => esc_html__( 'Center', 'elementor-pro' ),
|
||||
'icon' => 'eicon-align-center-h',
|
||||
],
|
||||
'end' => [
|
||||
'title' => esc_html__( 'End', 'elementor-pro' ),
|
||||
'icon' => 'eicon-align-end-h',
|
||||
],
|
||||
],
|
||||
'selectors' => [
|
||||
'{{WRAPPER}}' => '{{VALUE}};',
|
||||
],
|
||||
'selectors_dictionary' => [
|
||||
'start' => '--e-filter-item-justify-content: flex-start; --e-filter-item-align-items: flex-start; --e-filter-item-align-text: start;',
|
||||
'center' => '--e-filter-item-justify-content: center; --e-filter-item-align-items: center; --e-filter-item-align-text: center;',
|
||||
'end' => '--e-filter-item-justify-content: flex-end; --e-filter-item-align-items: flex-end; --e-filter-item-align-text: end;',
|
||||
],
|
||||
'condition' => [
|
||||
'direction' => 'vertical',
|
||||
'selected_element!' => '',
|
||||
'item_alignment_vertical' => 'stretch',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->end_controls_section();
|
||||
|
||||
$this->start_controls_section(
|
||||
'section_settings',
|
||||
[
|
||||
'label' => esc_html__( 'Settings', 'elementor-pro' ),
|
||||
'condition' => [
|
||||
'selected_element!' => '',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'heading_filter_logic',
|
||||
[
|
||||
'type' => Controls_Manager::HEADING,
|
||||
'label' => esc_html__( 'Filter Logic', 'elementor-pro' ),
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'multiple_selection',
|
||||
[
|
||||
'label' => esc_html__( 'Multiple Selection', 'elementor-pro' ),
|
||||
'type' => Controls_Manager::SWITCHER,
|
||||
'label_on' => esc_html__( 'Yes', 'elementor-pro' ),
|
||||
'label_off' => esc_html__( 'No', 'elementor-pro' ),
|
||||
'default' => 'no',
|
||||
'frontend_available' => true,
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'logical_combination',
|
||||
[
|
||||
'label' => esc_html__( 'Logical Combination', 'elementor-pro' ),
|
||||
'type' => Controls_Manager::SELECT,
|
||||
'default' => 'AND',
|
||||
'options' => [
|
||||
'AND' => esc_html__( 'AND', 'elementor-pro' ),
|
||||
'OR' => esc_html__( 'OR', 'elementor-pro' ),
|
||||
],
|
||||
'condition' => [
|
||||
'multiple_selection' => 'yes',
|
||||
],
|
||||
'separator' => 'after',
|
||||
'frontend_available' => true,
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'heading_displayed_elements',
|
||||
[
|
||||
'type' => Controls_Manager::HEADING,
|
||||
'label' => esc_html__( 'Displayed Elements', 'elementor-pro' ),
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'show_empty_items',
|
||||
[
|
||||
'label' => esc_html__( 'Empty Items', 'elementor-pro' ),
|
||||
'type' => Controls_Manager::SWITCHER,
|
||||
'default' => 'no',
|
||||
'label_off' => esc_html__( 'Hide', 'elementor-pro' ),
|
||||
'label_on' => esc_html__( 'Show', 'elementor-pro' ),
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'show_child_taxonomy',
|
||||
[
|
||||
'label' => esc_html__( 'Taxonomy Children', 'elementor-pro' ),
|
||||
'type' => Controls_Manager::SWITCHER,
|
||||
'default' => 'no',
|
||||
'label_off' => esc_html__( 'Hide', 'elementor-pro' ),
|
||||
'label_on' => esc_html__( 'Show', 'elementor-pro' ),
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'child_taxonomy_depth',
|
||||
[
|
||||
'label' => esc_html__( 'Depth', 'elementor-pro' ),
|
||||
'type' => Controls_Manager::SELECT,
|
||||
'default' => '1',
|
||||
'options' => [
|
||||
'1' => esc_html__( '1', 'elementor-pro' ),
|
||||
'2' => esc_html__( '2', 'elementor-pro' ),
|
||||
'3' => esc_html__( '3', 'elementor-pro' ),
|
||||
'4' => esc_html__( '4', 'elementor-pro' ),
|
||||
'5' => esc_html__( '5', 'elementor-pro' ),
|
||||
'6' => esc_html__( '6', 'elementor-pro' ),
|
||||
],
|
||||
'condition' => [
|
||||
'show_child_taxonomy' => 'yes',
|
||||
],
|
||||
]
|
||||
);
|
||||
$this->add_control(
|
||||
'show_first_item',
|
||||
[
|
||||
'label' => esc_html__( 'First Item', 'elementor-pro' ),
|
||||
'type' => Controls_Manager::SWITCHER,
|
||||
'default' => 'yes',
|
||||
'label_off' => esc_html__( 'Hide', 'elementor-pro' ),
|
||||
'label_on' => esc_html__( 'Show', 'elementor-pro' ),
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'first_item_title',
|
||||
[
|
||||
'label' => esc_html__( 'First Item Title', 'elementor-pro' ),
|
||||
'type' => Controls_Manager::TEXT,
|
||||
'default' => esc_html__( 'All', 'elementor-pro' ),
|
||||
'label_block' => true,
|
||||
'dynamic' => [
|
||||
'active' => true,
|
||||
],
|
||||
'condition' => [
|
||||
'show_first_item' => 'yes',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'number_of_taxonomies',
|
||||
[
|
||||
'label' => esc_html__( 'Number of taxonomies', 'elementor-pro' ),
|
||||
'type' => Controls_Manager::NUMBER,
|
||||
'min' => 1,
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_responsive_control(
|
||||
'horizontal_scroll',
|
||||
[
|
||||
'label' => esc_html__( 'Horizontal Scroll', 'elementor-pro' ),
|
||||
'type' => Controls_Manager::SELECT,
|
||||
'options' => [
|
||||
'disable' => esc_html__( 'Disable', 'elementor-pro' ),
|
||||
'enable' => esc_html__( 'Enable', 'elementor-pro' ),
|
||||
],
|
||||
'condition' => [
|
||||
'direction' => 'horizontal',
|
||||
],
|
||||
'selectors' => [
|
||||
'{{WRAPPER}}' => '{{VALUE}};',
|
||||
],
|
||||
'selectors_dictionary' => [
|
||||
'enable' => '--e-filter-wrap: nowrap; --e-filter-overflow-x: scroll;',
|
||||
'disable' => '--e-filter-wrap: wrap; --e-filter-overflow-x: initial;',
|
||||
],
|
||||
'default' => 'disable',
|
||||
'frontend_available' => true,
|
||||
'separator' => 'before',
|
||||
'description' => esc_html__( 'Scroll items if they don’t fit into their parent container', 'elementor-pro' ),
|
||||
]
|
||||
);
|
||||
|
||||
$this->end_controls_section();
|
||||
|
||||
$this->register_design_layout_controls();
|
||||
}
|
||||
|
||||
protected function register_design_layout_controls() {
|
||||
$this->start_controls_section(
|
||||
'section_design_layout',
|
||||
[
|
||||
'label' => esc_html__( 'Items', 'elementor-pro' ),
|
||||
'tab' => Controls_Manager::TAB_STYLE,
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_responsive_control(
|
||||
'taxonomy_filter_items_space_between',
|
||||
[
|
||||
'label' => esc_html__( 'Space between Items', 'elementor-pro' ),
|
||||
'type' => Controls_Manager::SLIDER,
|
||||
'size_units' => [ 'px', 'em', 'rem', 'custom' ],
|
||||
'range' => [
|
||||
'px' => [
|
||||
'max' => 200,
|
||||
],
|
||||
'em' => [
|
||||
'max' => 20,
|
||||
],
|
||||
'rem' => [
|
||||
'max' => 20,
|
||||
],
|
||||
],
|
||||
'selectors' => [
|
||||
'{{WRAPPER}}' => '--e-filter-space-between: {{SIZE}}{{UNIT}}',
|
||||
],
|
||||
'separator' => 'after',
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_group_control(
|
||||
Group_Control_Typography::get_type(),
|
||||
[
|
||||
'name' => 'taxonomy_filter_typography',
|
||||
'selector' => '{{WRAPPER}} .e-filter-item',
|
||||
]
|
||||
);
|
||||
|
||||
$this->start_controls_tabs( 'taxonomy_filter_tabs_section' );
|
||||
|
||||
$this->start_controls_tab( 'taxonomy_filter_tabs_normal', [ 'label' => esc_html__( 'Normal', 'elementor-pro' ) ] );
|
||||
|
||||
$this->add_control(
|
||||
'taxonomy_filter_normal_text_color',
|
||||
[
|
||||
'label' => esc_html__( 'Text Color', 'elementor-pro' ),
|
||||
'type' => Controls_Manager::COLOR,
|
||||
'selectors' => [
|
||||
'{{WRAPPER}}' => '--e-filter-normal-text-color: {{VALUE}};',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_group_control(
|
||||
Group_Control_Text_Shadow::get_type(),
|
||||
[
|
||||
'name' => 'taxonomy_filter_normal_text_shadow',
|
||||
'selector' => '{{WRAPPER}} .e-filter-item:not( [aria-pressed=true] ):not( :hover )',
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_group_control(
|
||||
Group_Control_Background::get_type(),
|
||||
[
|
||||
'name' => 'taxonomy_filter_normal_background',
|
||||
'selector' => '{{WRAPPER}} .e-filter-item:not( [aria-pressed=true] ):not( :hover )',
|
||||
'exclude' => [ 'image', 'video' ],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_group_control(
|
||||
Group_Control_Border::get_type(),
|
||||
[
|
||||
'name' => 'taxonomy_filter_normal_border',
|
||||
'selector' => '{{WRAPPER}} .e-filter-item:not( [aria-pressed=true] ):not( :hover )',
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_group_control(
|
||||
Group_Control_Box_Shadow::get_type(),
|
||||
[
|
||||
'name' => 'taxonomy_filter_normal_box_shadow',
|
||||
'selector' => '{{WRAPPER}} .e-filter-item:not( [aria-pressed=true] ):not( :hover )',
|
||||
]
|
||||
);
|
||||
|
||||
$this->end_controls_tab();
|
||||
|
||||
$this->start_controls_tab( 'taxonomy_filter_tabs_hover', [ 'label' => esc_html__( 'Hover', 'elementor-pro' ) ] );
|
||||
|
||||
$this->add_control(
|
||||
'taxonomy_filter_hover_text_color',
|
||||
[
|
||||
'label' => esc_html__( 'Text Color', 'elementor-pro' ),
|
||||
'type' => Controls_Manager::COLOR,
|
||||
'selectors' => [
|
||||
'{{WRAPPER}}' => '--e-filter-hover-text-color: {{VALUE}};',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_group_control(
|
||||
Group_Control_Text_Shadow::get_type(),
|
||||
[
|
||||
'name' => 'taxonomy_filter_hover_text_shadow',
|
||||
'selector' => '{{WRAPPER}} .e-filter-item:hover:not( [aria-pressed=true] )',
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_group_control(
|
||||
Group_Control_Background::get_type(),
|
||||
[
|
||||
'name' => 'taxonomy_filter_hover_background',
|
||||
'selector' => '{{WRAPPER}} .e-filter-item:hover:not( [aria-pressed=true] )',
|
||||
'exclude' => [ 'image', 'video' ],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_group_control(
|
||||
Group_Control_Border::get_type(),
|
||||
[
|
||||
'name' => 'taxonomy_filter_hover_border',
|
||||
'selector' => '{{WRAPPER}} .e-filter-item:hover:not( [aria-pressed=true] )',
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_group_control(
|
||||
Group_Control_Box_Shadow::get_type(),
|
||||
[
|
||||
'name' => 'taxonomy_filter_hover_box_shadow',
|
||||
'selector' => '{{WRAPPER}} .e-filter-item:hover:not( [aria-pressed=true] )',
|
||||
]
|
||||
);
|
||||
|
||||
$this->end_controls_tab();
|
||||
|
||||
$this->start_controls_tab( 'taxonomy_filter_tabs_active', [ 'label' => esc_html__( 'Active', 'elementor-pro' ) ] );
|
||||
|
||||
$this->add_control(
|
||||
'taxonomy_filter_active_text_color',
|
||||
[
|
||||
'label' => esc_html__( 'Text Color', 'elementor-pro' ),
|
||||
'type' => Controls_Manager::COLOR,
|
||||
'selectors' => [
|
||||
'{{WRAPPER}}' => '--e-filter-active-text-color: {{VALUE}};',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_group_control(
|
||||
Group_Control_Text_Shadow::get_type(),
|
||||
[
|
||||
'name' => 'taxonomy_filter_active_text_shadow',
|
||||
'selector' => '{{WRAPPER}} .e-filter-item[aria-pressed="true"]',
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_group_control(
|
||||
Group_Control_Background::get_type(),
|
||||
[
|
||||
'name' => 'taxonomy_filter_active_background',
|
||||
'selector' => '{{WRAPPER}} .e-filter-item[aria-pressed="true"]',
|
||||
'exclude' => [ 'image', 'video' ],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_group_control(
|
||||
Group_Control_Border::get_type(),
|
||||
[
|
||||
'name' => 'taxonomy_filter_active_border',
|
||||
'selector' => '{{WRAPPER}} .e-filter-item[aria-pressed="true"]',
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_group_control(
|
||||
Group_Control_Box_Shadow::get_type(),
|
||||
[
|
||||
'name' => 'taxonomy_filter_active_box_shadow',
|
||||
'selector' => '{{WRAPPER}} .e-filter-item[aria-pressed="true"]',
|
||||
]
|
||||
);
|
||||
|
||||
$this->end_controls_tab();
|
||||
|
||||
$this->end_controls_tabs();
|
||||
|
||||
$this->add_responsive_control(
|
||||
'taxonomy_filter_border_radius',
|
||||
[
|
||||
'label' => esc_html__( 'Border Radius', 'elementor-pro' ),
|
||||
'type' => Controls_Manager::DIMENSIONS,
|
||||
'size_units' => [ 'px', '%', 'em', 'rem', 'custom' ],
|
||||
'selectors' => [
|
||||
'{{WRAPPER}}' => '--e-filter-item-border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
|
||||
],
|
||||
'separator' => 'before',
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_responsive_control(
|
||||
'taxonomy_filter_padding',
|
||||
[
|
||||
'label' => esc_html__( 'Padding', 'elementor-pro' ),
|
||||
'type' => Controls_Manager::DIMENSIONS,
|
||||
'size_units' => [ 'px', '%', 'em', 'rem', 'vm', 'custom' ],
|
||||
'selectors' => [
|
||||
'{{WRAPPER}} .e-filter-item' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}}',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->end_controls_section();
|
||||
}
|
||||
|
||||
protected function get_empty_widget_message_by_key( $message_key ) {
|
||||
$messages = [
|
||||
'select_loop_widget' => esc_html__( 'Choose a Loop Grid to view the Taxonomy Filter.', 'elementor-pro' ),
|
||||
'no_taxonomy_selected' => esc_html__( 'Please select a taxonomy.', 'elementor-pro' ),
|
||||
'no_terms_found' => esc_html__( 'No taxonomy terms found.', 'elementor-pro' ),
|
||||
];
|
||||
|
||||
return $messages[ $message_key ];
|
||||
}
|
||||
|
||||
protected function print_empty_results_if_editor( $message_key ) {
|
||||
if ( ! Plugin::elementor()->editor->is_edit_mode() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
?>
|
||||
<div class="e-filter-empty">
|
||||
<?php echo esc_html( $this->get_empty_widget_message_by_key( $message_key ) ); ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
private function has_empty_results( $selected_element, $user_selected_taxonomy, $terms ): bool {
|
||||
if ( empty( $selected_element ) ) {
|
||||
$this->print_empty_results_if_editor( 'select_loop_widget' );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( empty( $user_selected_taxonomy ) ) {
|
||||
$this->print_empty_results_if_editor( 'no_taxonomy_selected' );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( empty( $terms ) ) {
|
||||
$this->print_empty_results_if_editor( 'no_terms_found' );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function get_loop_widget_settings() {
|
||||
$document = Plugin::elementor()->documents->get_doc_for_frontend( $this->get_current_ID() );
|
||||
|
||||
if ( ! $document ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$widget_data = Utils::find_element_recursive( $document->get_elements_data(), $this->get_settings_for_display( 'selected_element' ) );
|
||||
|
||||
return ! empty( $widget_data['settings'] ) ? $widget_data['settings'] : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
private function get_current_ID() {
|
||||
$post_id = 0;
|
||||
$theme_builder = ThemeBuilderModule::instance();
|
||||
$location = $theme_builder->get_locations_manager()->get_current_location();
|
||||
$documents = $theme_builder->get_conditions_manager()->get_documents_for_location( $location );
|
||||
|
||||
if ( empty( $documents ) ) {
|
||||
return get_the_ID();
|
||||
}
|
||||
|
||||
foreach ( $documents as $document ) {
|
||||
$post_id = $document->get_post()->ID;
|
||||
}
|
||||
|
||||
return $post_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
private function is_term_excluded_by_query_control( $term, $loop_filter_module ) {
|
||||
$loop_widget_settings = $this->get_loop_widget_settings();
|
||||
$skin = ! empty( $loop_widget_settings['_skin'] ) ? $loop_widget_settings['_skin'] : 'post';
|
||||
|
||||
return $loop_filter_module->is_term_not_selected_for_inclusion( $loop_widget_settings, $term, $skin )
|
||||
|| $loop_filter_module->is_term_selected_for_exclusion( $loop_widget_settings, $term, $skin )
|
||||
|| $loop_filter_module->should_exclude_term_by_manual_selection( $loop_widget_settings, $term, $this->get_settings_for_display( 'taxonomy' ), $skin );
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$settings = $this->get_settings_for_display();
|
||||
$selected_element = $settings['selected_element'];
|
||||
$user_selected_taxonomy = $settings['taxonomy'];
|
||||
|
||||
$terms = $this->get_filtered_taxonomy_terms( $user_selected_taxonomy, $settings );
|
||||
|
||||
if ( $this->has_empty_results( $selected_element, $user_selected_taxonomy, $terms ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$active_filters = [];
|
||||
$loop_filter_module = Plugin::instance()->modules_manager->get_modules( 'loop-filter' );
|
||||
$query_string_filters = $loop_filter_module->get_query_string_filters();
|
||||
|
||||
if ( array_key_exists( $selected_element, $query_string_filters ) ) {
|
||||
$active_filters = $query_string_filters[ $selected_element ]['taxonomy'];
|
||||
}
|
||||
|
||||
$active_terms = 0;
|
||||
$total_taxonomies = 0;
|
||||
$number_of_taxonomies = $settings['number_of_taxonomies'];
|
||||
|
||||
$this->add_render_attribute( 'filter-bar', [
|
||||
'class' => 'e-filter',
|
||||
'role' => 'search', // BC for older browser versions that don't support `<search>` element.
|
||||
'data-base-url' => $this->get_base_url(),
|
||||
'data-page-num' => max( 1, get_query_var( 'paged' ), get_query_var( 'page' ) ),
|
||||
] );
|
||||
?>
|
||||
<search <?php $this->print_render_attribute_string( 'filter-bar' ); ?>>
|
||||
<?php foreach ( $terms as $term ) {
|
||||
$total_taxonomies++;
|
||||
$aria_pressed_value = 'false';
|
||||
|
||||
if ( ! isset( $term->taxonomy ) || $this->is_term_excluded_by_query_control( $term, $loop_filter_module ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$term_taxonomy = $term->taxonomy;
|
||||
|
||||
if ( array_key_exists( $term_taxonomy, $active_filters ) && in_array( urldecode( $term->slug ), $active_filters[ $term_taxonomy ]['terms'] ) ) {
|
||||
$aria_pressed_value = 'true';
|
||||
$active_terms++;
|
||||
}
|
||||
|
||||
if ( ! empty( $number_of_taxonomies ) && $total_taxonomies > $number_of_taxonomies ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// This filter allows us to write the slug with non-latin characters as well, such as Hebrew.
|
||||
$slug = apply_filters( 'editable_slug', $term->slug, $term );
|
||||
?>
|
||||
<button class="e-filter-item" data-filter="<?php echo esc_attr( $slug ); ?>" aria-pressed="<?php echo esc_attr( $aria_pressed_value ); ?>"><?php echo esc_html( $term->name ); ?></button>
|
||||
<?php } ?>
|
||||
|
||||
<?php
|
||||
$aria_pressed_value = ( 0 === $active_terms ) ? 'true' : 'false';
|
||||
?>
|
||||
<?php if ( 'yes' === $settings['show_first_item'] ) : ?>
|
||||
<button class="e-filter-item" data-filter="__all" aria-pressed="<?php echo esc_attr( $aria_pressed_value ); ?>">
|
||||
<?php echo $settings['first_item_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</search>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $user_selected_taxonomy
|
||||
* @param array $settings
|
||||
* @return void|\WP_Term[]
|
||||
*/
|
||||
private function get_filtered_taxonomy_terms( $user_selected_taxonomy, $settings ) {
|
||||
$display_settings = $this->get_settings_for_display();
|
||||
$settings['taxonomy'] = $user_selected_taxonomy;
|
||||
|
||||
return $this->get_filtered_taxonomies( $settings, $display_settings );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user