389 lines
14 KiB
PHP
389 lines
14 KiB
PHP
<?php
|
|
namespace ElementorPro\Modules\LoopBuilder\Providers;
|
|
|
|
use ElementorPro\Core\Utils;
|
|
use ElementorPro\Modules\LoopBuilder\Module as LoopBuilderModule;
|
|
use ElementorPro\Modules\Woocommerce\Module as WoocommerceModule;
|
|
use Elementor\Controls_Manager;
|
|
use ElementorPro\Modules\QueryControl\Module as QueryControlModule;
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit; // Exit if accessed directly
|
|
}
|
|
|
|
class Taxonomy_Loop_Provider {
|
|
// QUERY TABS
|
|
const TABS_WRAPPER = 'query_args';
|
|
const INCLUDE_TAB = 'query_include';
|
|
const EXCLUDE_TAB = 'query_exclude';
|
|
|
|
// QUERY CONTROL KEYS
|
|
const QUERY_CONTROL_GROUP_NAME = LoopBuilderModule::QUERY_ID;
|
|
const POST_TYPE = 'post_type';
|
|
const FILTER_BY = 'filter_by';
|
|
const PARENT = 'child_of';
|
|
const INCLUDE = 'posts_ids';
|
|
const EXCLUDE = 'exclude_ids';
|
|
const AVOID_DUPLICATES = 'avoid_duplicates';
|
|
const OFFSET = 'offset';
|
|
const ORDER_BY = 'orderby';
|
|
const ORDER = 'order';
|
|
const HIDE_EMPTY = 'hide_empty';
|
|
const HIERARCHICAL = 'hierarchical';
|
|
const QUERY_DEPTH = 'child_taxonomy_depth';
|
|
const QUERY_ID = 'term_taxonomy_id';
|
|
|
|
// DEFAULT TAXONOMIES
|
|
const POST_CATEGORY_TAXONOMY = 'category';
|
|
const POST_TAG_TAXONOMY = 'post_tag';
|
|
const PRODUCT_CATEGORY_TAXONOMY = 'product_cat';
|
|
const PRODUCT_TAG_TAXONOMY = 'product_tag';
|
|
|
|
// FILTER_BY OPTION KEYs
|
|
const MANUAL_SELECTION = 'manual_selection';
|
|
const SHOW_ALL = 'show_all';
|
|
const BY_PARENT = 'by_parent';
|
|
const CURRENT_QUERY = 'current_query';
|
|
const CURRENT_SUBCATEGORIES = 'current_subcategories';
|
|
|
|
// ORDER_BY OPTION KEYS
|
|
const ORDER_BY_NAME = 'name';
|
|
const ORDER_BY_ID = 'term_id';
|
|
|
|
// ORDER OPTION KEYS
|
|
const ASC_ORDER = 'ASC';
|
|
const DESC_ORDER = 'DESC';
|
|
|
|
private $skin_id;
|
|
private $default_source_type;
|
|
|
|
public function __construct( $skin_id = LoopBuilderModule::LOOP_POST_TAXONOMY_SKIN_ID, $default_source_type = self::POST_CATEGORY_TAXONOMY ) {
|
|
$this->skin_id = $skin_id;
|
|
$this->default_source_type = $default_source_type;
|
|
}
|
|
|
|
public function get_query_settings( array $display_settings ): array {
|
|
$taxonomy_type = $display_settings[ $this->get_query_property_name( self::POST_TYPE ) ] ?? self::POST_CATEGORY_TAXONOMY;
|
|
$term_taxonomy_id = $display_settings[ $this->get_property_name( self::QUERY_ID ) ];
|
|
$depth = (int) $display_settings[ $this->get_property_name( self::QUERY_DEPTH ) ];
|
|
$hierarchical = $display_settings[ $this->get_property_name( self::HIERARCHICAL ) ];
|
|
|
|
$settings = [
|
|
'taxonomy' => empty( $taxonomy_type ) ? $this->default_source_type : $taxonomy_type,
|
|
'show_empty_items' => 'yes' === $display_settings[ $this->get_property_name( self::HIDE_EMPTY ) ] ? 'no' : 'yes',
|
|
'include' => $display_settings[ $this->get_property_name( self::INCLUDE ) ],
|
|
'exclude' => $display_settings[ $this->get_property_name( self::EXCLUDE ) ],
|
|
'term_taxonomy_id' => strlen( $term_taxonomy_id ) ? $term_taxonomy_id : null,
|
|
'child_of' => $display_settings[ $this->get_property_name( self::PARENT ) ],
|
|
'offset' => $display_settings[ $this->get_property_name( self::OFFSET ) ],
|
|
'number' => $display_settings['posts_per_page'],
|
|
'orderby' => $display_settings[ $this->get_property_name( self::ORDER_BY ) ],
|
|
'order' => $display_settings[ $this->get_property_name( self::ORDER ) ],
|
|
'avoid_reset_parent' => true,
|
|
'show_child_taxonomy' => 'no',
|
|
];
|
|
|
|
if ( $depth && 'yes' === $hierarchical ) {
|
|
$settings['hierarchical'] = 'yes';
|
|
$settings['child_taxonomy_depth'] = $depth - 1;
|
|
$settings['show_child_taxonomy'] = 'yes';
|
|
}
|
|
|
|
return $settings;
|
|
}
|
|
|
|
public function get_control_args( string $key, bool $is_prefixed ): array {
|
|
$tabs_wrapper = $this->get_query_property_name( self::TABS_WRAPPER );
|
|
$include_tab = $this->get_query_property_name( self::INCLUDE_TAB );
|
|
$exclude_tab = $this->get_query_property_name( self::EXCLUDE_TAB );
|
|
$include_exclude_conditions = $this->get_include_exclude_conditions( $is_prefixed );
|
|
$filter_related_conditions = $this->get_depth_related_filter_conditions( $is_prefixed, false );
|
|
|
|
$post_type_key = $is_prefixed ? $this->get_query_property_name( self::POST_TYPE ) : self::POST_TYPE;
|
|
$filter_by_key = $is_prefixed ? $this->get_query_property_name( self::FILTER_BY ) : self::FILTER_BY;
|
|
$hierarchical_key = $is_prefixed ? $this->get_property_name( self::HIERARCHICAL ) : self::HIERARCHICAL;
|
|
|
|
$filter_by_parent_term = Utils::format_control_condition( $filter_by_key, '===', self::PARENT );
|
|
$is_hierarchical_term = Utils::format_control_condition( $hierarchical_key, '===', 'yes' );
|
|
|
|
$control_options = [
|
|
self::FILTER_BY => [
|
|
'label' => esc_html__( 'Filter By', 'elementor-pro' ),
|
|
'type' => Controls_Manager::SELECT,
|
|
'default' => 'show_all',
|
|
'options' => [
|
|
self::SHOW_ALL => esc_html__( 'Show All', 'elementor-pro' ),
|
|
self::MANUAL_SELECTION => esc_html__( 'Manual Selection', 'elementor-pro' ),
|
|
],
|
|
'conditions' => $filter_related_conditions,
|
|
],
|
|
self::PARENT => [
|
|
'label' => esc_html__( 'Parent', 'elementor-pro' ),
|
|
'type' => Controls_Manager::SELECT,
|
|
'default' => '',
|
|
'options' => [ 1, 2, 3, 4 ],
|
|
'conditions' => [
|
|
'relation' => 'and',
|
|
'terms' => $this->get_related_tags_conditions( $post_type_key, '!==', [ $filter_by_parent_term ] ),
|
|
],
|
|
'tabs_wrapper' => $tabs_wrapper,
|
|
'inner_tab' => $include_tab,
|
|
],
|
|
self::INCLUDE_TAB => [
|
|
'type' => Controls_Manager::TAB,
|
|
'label' => esc_html__( 'Include', 'elementor-pro' ),
|
|
'tabs_wrapper' => $tabs_wrapper,
|
|
'conditions' => $include_exclude_conditions,
|
|
],
|
|
self::EXCLUDE_TAB => [
|
|
'type' => Controls_Manager::TAB,
|
|
'label' => esc_html__( 'Exclude', 'elementor-pro' ),
|
|
'tabs_wrapper' => $tabs_wrapper,
|
|
'conditions' => $include_exclude_conditions,
|
|
],
|
|
self::INCLUDE => [
|
|
'label' => esc_html__( 'Include By', 'elementor-pro' ),
|
|
'type' => QueryControlModule::QUERY_CONTROL_ID,
|
|
'label_block' => true,
|
|
'multiple' => true,
|
|
'autocomplete' => [
|
|
'object' => QueryControlModule::QUERY_OBJECT_TAX,
|
|
'autocomplete' => [],
|
|
],
|
|
'default' => '',
|
|
'conditions' => $include_exclude_conditions,
|
|
'tabs_wrapper' => $tabs_wrapper,
|
|
'inner_tab' => $include_tab,
|
|
],
|
|
self::EXCLUDE => [
|
|
'label' => esc_html__( 'Search & Select', 'elementor-pro' ),
|
|
'type' => QueryControlModule::QUERY_CONTROL_ID,
|
|
'label_block' => true,
|
|
'multiple' => true,
|
|
'autocomplete' => [
|
|
'object' => QueryControlModule::QUERY_OBJECT_TAX,
|
|
],
|
|
'default' => '',
|
|
'conditions' => $include_exclude_conditions,
|
|
'tabs_wrapper' => $tabs_wrapper,
|
|
'inner_tab' => $exclude_tab,
|
|
],
|
|
self::ORDER_BY => [
|
|
'label' => esc_html__( 'Order By', 'elementor-pro' ),
|
|
'type' => Controls_Manager::SELECT,
|
|
'default' => self::ORDER_BY_NAME,
|
|
'options' => [
|
|
self::ORDER_BY_NAME => esc_html__( 'Name', 'elementor-pro' ),
|
|
self::ORDER_BY_ID => esc_html__( 'ID', 'elementor-pro' ),
|
|
],
|
|
'separator' => 'before',
|
|
'tabs_wrapper' => $tabs_wrapper,
|
|
'inner_tab' => $include_tab,
|
|
'condition' => [],
|
|
],
|
|
self::ORDER => [
|
|
'label' => esc_html__( 'Order', 'elementor-pro' ),
|
|
'type' => Controls_Manager::SELECT,
|
|
'default' => self::DESC_ORDER,
|
|
'options' => [
|
|
self::ASC_ORDER => esc_html__( 'ASC', 'elementor-pro' ),
|
|
self::DESC_ORDER => esc_html__( 'DESC', 'elementor-pro' ),
|
|
],
|
|
'tabs_wrapper' => $tabs_wrapper,
|
|
'inner_tab' => $include_tab,
|
|
'condition' => [],
|
|
],
|
|
self::AVOID_DUPLICATES => [
|
|
'label' => esc_html__( 'Avoid Duplicates', 'elementor-pro' ),
|
|
'type' => Controls_Manager::SWITCHER,
|
|
'default' => 'no',
|
|
'description' => esc_html__( 'Set to Yes to avoid duplicate posts from showing up. This only effects the frontend.', 'elementor-pro' ),
|
|
'tabs_wrapper' => $tabs_wrapper,
|
|
'inner_tab' => $exclude_tab,
|
|
],
|
|
self::HIDE_EMPTY => [
|
|
'label' => esc_html__( 'Hide Empty', 'elementor-pro' ),
|
|
'type' => Controls_Manager::SWITCHER,
|
|
'default' => 'no',
|
|
'separator' => 'before',
|
|
'condition' => [],
|
|
'tabs_wrapper' => $tabs_wrapper,
|
|
'inner_tab' => $include_tab,
|
|
],
|
|
self::OFFSET => [
|
|
'label' => esc_html__( 'Skip Taxonomy', 'elementor-pro' ),
|
|
'type' => Controls_Manager::NUMBER,
|
|
'default' => 0,
|
|
'description' => esc_html__( 'Start grid from chosen taxonomy', 'elementor-pro' ),
|
|
'tabs_wrapper' => $tabs_wrapper,
|
|
'inner_tab' => $exclude_tab,
|
|
],
|
|
self::HIERARCHICAL => [
|
|
'label' => esc_html__( 'Filter by depth', 'elementor-pro' ),
|
|
'type' => Controls_Manager::SWITCHER,
|
|
'default' => 'no',
|
|
'conditions' => $this->get_depth_related_filter_conditions( $is_prefixed, true ),
|
|
'tabs_wrapper' => $tabs_wrapper,
|
|
'inner_tab' => $include_tab,
|
|
],
|
|
self::QUERY_DEPTH => [
|
|
'label' => esc_html__( 'Depth', 'elementor-pro' ),
|
|
'type' => Controls_Manager::SELECT,
|
|
'default' => '0',
|
|
'options' => [
|
|
'0' => esc_html__( 'All', 'elementor-pro' ),
|
|
'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' ),
|
|
],
|
|
'conditions' => $this->get_depth_related_filter_conditions( $is_prefixed, true, [ $is_hierarchical_term ] ),
|
|
'tabs_wrapper' => $tabs_wrapper,
|
|
'inner_tab' => $include_tab,
|
|
],
|
|
self::QUERY_ID => [
|
|
'label' => esc_html__( 'Query ID', 'elementor-pro' ),
|
|
'type' => Controls_Manager::TEXT,
|
|
'default' => '',
|
|
'ai' => [
|
|
'active' => false,
|
|
],
|
|
'description' => esc_html__( 'Give your Query a custom unique id to allow server side filtering', 'elementor-pro' ),
|
|
'separator' => 'before',
|
|
'dynamic' => [
|
|
'active' => true,
|
|
],
|
|
'tabs_wrapper' => $tabs_wrapper,
|
|
'inner_tab' => $include_tab,
|
|
],
|
|
];
|
|
|
|
return $control_options[ $key ] ?? $control_options;
|
|
}
|
|
|
|
/**
|
|
* Get settings key names.
|
|
*
|
|
* Adds prefix to the desired key.
|
|
*/
|
|
public function get_property_name( string $key ): string {
|
|
return $this->skin_id . '_' . $key;
|
|
}
|
|
|
|
/**
|
|
* Get query settings key names.
|
|
*
|
|
* Adds prefix and '_query_' to the desired key.
|
|
*/
|
|
public function get_query_property_name( string $key ): string {
|
|
return $this->get_property_name( 'query_' . $key );
|
|
}
|
|
|
|
public static function is_source_type_taxonomy( $source_type ) {
|
|
$available_source_types = self::get_loop_taxonomy_types();
|
|
|
|
return in_array( $source_type, $available_source_types, true );
|
|
}
|
|
|
|
public static function get_loop_taxonomy_types() {
|
|
return [
|
|
LoopBuilderModule::LOOP_POST_TAXONOMY_SKIN_ID,
|
|
WoocommerceModule::LOOP_PRODUCT_TAXONOMY_SKIN_ID,
|
|
];
|
|
}
|
|
|
|
public static function get_default_source_type( $taxonomy_loop_type, $prefix = '' ): string {
|
|
$options = [
|
|
WoocommerceModule::LOOP_PRODUCT_TAXONOMY_SKIN_ID => self::PRODUCT_CATEGORY_TAXONOMY,
|
|
LoopBuilderModule::LOOP_POST_TAXONOMY_SKIN_ID => self::POST_CATEGORY_TAXONOMY,
|
|
];
|
|
|
|
return isset( $options[ $taxonomy_loop_type ] )
|
|
? $prefix . $options[ $taxonomy_loop_type ]
|
|
: '';
|
|
}
|
|
|
|
public static function get_supported_cpts( $taxonomy_loop_type ) {
|
|
$post_types = array_merge( [ 'post' ], self::get_post_additional_cpts() );
|
|
$options = [
|
|
WoocommerceModule::LOOP_PRODUCT_TAXONOMY_SKIN_ID => [ 'product' ],
|
|
'product' => [ 'product' ],
|
|
LoopBuilderModule::LOOP_POST_TAXONOMY_SKIN_ID => $post_types,
|
|
'post' => $post_types,
|
|
];
|
|
|
|
return $options[ $taxonomy_loop_type ] ?? [];
|
|
}
|
|
|
|
private function get_include_exclude_conditions( bool $is_prefixed = false ): array {
|
|
$post_type_name = $is_prefixed ? $this->get_query_property_name( self::POST_TYPE ) : self::POST_TYPE;
|
|
$filter_by_key = $is_prefixed ? $this->get_query_property_name( self::FILTER_BY ) : self::FILTER_BY;
|
|
|
|
$manual_selection_terms = Utils::format_control_condition( $filter_by_key, '===', self::MANUAL_SELECTION );
|
|
|
|
return [
|
|
'relation' => 'or',
|
|
'terms' => $this->get_related_tags_conditions( $post_type_name, '===', [ $manual_selection_terms ] ),
|
|
];
|
|
}
|
|
|
|
private function get_depth_related_filter_conditions( $is_prefixed, $should_add_hierarchy_condition, $extra_terms = [] ) {
|
|
$filter_by_key = $is_prefixed ? $this->get_query_property_name( self::FILTER_BY ) : self::FILTER_BY;
|
|
$post_type_key = $is_prefixed ? $this->get_query_property_name( self::POST_TYPE ) : self::POST_TYPE;
|
|
|
|
$conditions = [
|
|
'relation' => 'and',
|
|
'terms' => $this->get_related_tags_conditions( $post_type_key, '!==' ),
|
|
];
|
|
|
|
if ( $should_add_hierarchy_condition ) {
|
|
$conditions['terms'][] = Utils::format_control_condition( $filter_by_key, '!==', self::MANUAL_SELECTION );
|
|
}
|
|
|
|
$conditions['terms'] = array_merge( $conditions['terms'], $extra_terms );
|
|
|
|
return $conditions;
|
|
}
|
|
|
|
private function get_related_tags_conditions( $name, $operator, $extra_terms = [] ) {
|
|
$tags = [ self::POST_TAG_TAXONOMY, self::PRODUCT_TAG_TAXONOMY ];
|
|
|
|
return $this->get_related_taxonomy_condition_terms( $tags, $name, $operator, $extra_terms );
|
|
}
|
|
|
|
private function get_related_taxonomy_condition_terms( $value_options, $name, $operator, $extra_terms = [] ) {
|
|
$terms = [];
|
|
|
|
foreach ( $value_options as $value ) {
|
|
$terms[] = Utils::format_control_condition( $name, $operator, $value );
|
|
}
|
|
|
|
return array_merge( $terms, $extra_terms );
|
|
}
|
|
|
|
public static function is_loop_taxonomy(): bool {
|
|
global $wp_query;
|
|
|
|
return $wp_query->is_loop_taxonomy ?? false;
|
|
}
|
|
|
|
public static function is_loop_taxonomy_strict(): bool {
|
|
global $wp_query;
|
|
|
|
return self::is_loop_taxonomy() && ( $wp_query->loop_term ?? false );
|
|
}
|
|
|
|
private static function get_post_additional_cpts() {
|
|
$post_types = get_post_types( [
|
|
'public' => true,
|
|
'_builtin' => false,
|
|
] );
|
|
|
|
return array_filter( $post_types, function( $post_type ) {
|
|
return ! in_array( $post_type, [ 'product', 'e-landing-page', 'elementor_library' ] );
|
|
} );
|
|
}
|
|
}
|