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:
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 );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user