add_control( 'posts_source', [ 'label' => __('Source', 'ultimate-post-kit'), 'type' => Controls_Manager::SELECT, 'options' => $this->getGroupControlQueryPostTypes(), 'default' => 'post', ] ); $this->add_control( 'posts_source_description', [ 'label' => '', 'type' => Controls_Manager::RAW_HTML, 'raw' => __('This current query may not be display posts in the editor. Please save and refresh the page to see the posts.', 'ultimate-post-kit'), 'content_classes' => 'elementor-panel-alert elementor-panel-alert-info', 'condition' => [ 'posts_source' => 'current_query', ], ] ); $this->add_control( 'posts_source_description_2', [ 'label' => '', 'type' => Controls_Manager::RAW_HTML, 'raw' => sprintf( /* translators: 1: HTML link to options-reading.php */ __('Posts per page is global for current query. Set "Blog pages show at most" from settings page of WordPress. Go to Settings > Reading.', 'ultimate-post-kit'), esc_url(admin_url('options-reading.php')) ), 'content_classes' => 'elementor-panel-alert elementor-panel-alert-info', 'condition' => [ 'posts_source' => 'current_query', ], ] ); $this->add_control( 'posts_selected_ids', [ 'label' => __('Search & Select', 'ultimate-post-kit'), 'type' => Dynamic_Select::TYPE, 'multiple' => true, 'label_block' => true, 'query_args' => [ 'query' => 'posts', ], 'condition' => [ 'posts_source' => 'manual_selection', ] ] ); $this->start_controls_tabs( 'tabs_posts_include_exclude', [ 'condition' => [ 'posts_source!' => ['manual_selection', 'current_query'], ] ] ); $this->start_controls_tab( 'tab_posts_include', [ 'label' => __('Include', 'ultimate-post-kit'), 'condition' => [ 'posts_source!' => ['manual_selection', 'current_query'], ] ] ); $this->add_control( 'posts_include_by', [ 'label' => __('Include By', 'ultimate-post-kit'), 'type' => Controls_Manager::SELECT2, 'multiple' => true, 'label_block' => true, 'options' => [ 'authors' => __('Authors', 'ultimate-post-kit'), 'terms' => __('Terms', 'ultimate-post-kit'), ], 'condition' => [ 'posts_source!' => ['manual_selection', 'current_query'], ] ] ); $this->add_control( 'posts_include_author_ids', [ 'label' => __('Authors', 'ultimate-post-kit'), 'type' => Dynamic_Select::TYPE, 'multiple' => true, 'label_block' => true, 'query_args' => [ 'query' => 'authors', ], 'condition' => [ 'posts_include_by' => 'authors', 'posts_source!' => ['manual_selection', 'current_query'], ] ] ); $this->add_control( 'posts_include_term_ids', [ 'label' => __('Terms', 'ultimate-post-kit'), 'description' => __('Terms are items in a taxonomy. The available taxonomies are: Categories, Tags, Formats and custom taxonomies.', 'ultimate-post-kit'), 'type' => Dynamic_Select::TYPE, 'multiple' => true, 'label_block' => true, 'placeholder' => __('Type and select terms', 'ultimate-post-kit'), 'query_args' => [ 'query' => 'terms', 'widget_props' => [ 'post_type' => 'posts_source' ] ], 'condition' => [ 'posts_include_by' => 'terms', 'posts_source!' => ['manual_selection', 'current_query'], ] ] ); $this->end_controls_tab(); $this->start_controls_tab( 'tab_posts_exclude', [ 'label' => __('Exclude', 'ultimate-post-kit'), 'condition' => [ 'posts_source!' => ['manual_selection', 'current_query'], ] ] ); $this->add_control( 'posts_exclude_by', [ 'label' => __('Exclude By', 'ultimate-post-kit'), 'type' => Controls_Manager::SELECT2, 'multiple' => true, 'label_block' => true, 'options' => [ 'authors' => __('Authors', 'ultimate-post-kit'), 'current_post' => __('Current Post', 'ultimate-post-kit'), 'manual_selection' => __('Manual Selection', 'ultimate-post-kit'), 'terms' => __('Terms', 'ultimate-post-kit'), ], 'condition' => [ 'posts_source!' => ['manual_selection', 'current_query'], ] ] ); $this->add_control( 'posts_exclude_ids', [ 'label' => __('Search & Select', 'ultimate-post-kit'), 'type' => Dynamic_Select::TYPE, 'multiple' => true, 'label_block' => true, 'query_args' => [ 'query' => 'posts', 'widget_props' => [ 'post_type' => 'posts_source' ] ], 'condition' => [ 'posts_source!' => ['manual_selection', 'current_query'], 'posts_exclude_by' => 'manual_selection', ] ] ); $this->add_control( 'posts_exclude_author_ids', [ 'label' => __('Authors', 'ultimate-post-kit'), 'type' => Dynamic_Select::TYPE, 'multiple' => true, 'label_block' => true, 'query_args' => [ 'query' => 'authors', ], 'condition' => [ 'posts_exclude_by' => 'authors', 'posts_source!' => ['manual_selection', 'current_query'], ] ] ); $this->add_control( 'posts_exclude_term_ids', [ 'label' => __('Terms', 'ultimate-post-kit'), 'description' => __('Terms are items in a taxonomy. The available taxonomies are: Categories, Tags, Formats and custom taxonomies.', 'ultimate-post-kit'), 'type' => Dynamic_Select::TYPE, 'multiple' => true, 'label_block' => true, 'placeholder' => __('Type and select terms', 'ultimate-post-kit'), 'query_args' => [ 'query' => 'terms', 'widget_props' => [ 'post_type' => 'posts_source' ] ], 'condition' => [ 'posts_exclude_by' => 'terms', 'posts_source!' => ['manual_selection', 'current_query'], ] ] ); $this->end_controls_tab(); $this->end_controls_tabs(); $this->add_control( 'posts_divider', [ 'type' => Controls_Manager::DIVIDER, 'condition' => [ 'posts_source!' => 'current_query', ] ] ); $this->add_control( 'posts_offset', [ 'label' => __('Offset', 'ultimate-post-kit'), 'type' => Controls_Manager::NUMBER, 'default' => 0, ] ); $this->add_control( 'posts_select_date', [ 'label' => __('Date', 'ultimate-post-kit'), 'type' => Controls_Manager::SELECT, 'default' => 'anytime', 'options' => [ 'anytime' => __('All', 'ultimate-post-kit'), 'today' => __('Past Day', 'ultimate-post-kit'), 'week' => __('Past Week', 'ultimate-post-kit'), 'month' => __('Past Month', 'ultimate-post-kit'), 'quarter' => __('Past Quarter', 'ultimate-post-kit'), 'year' => __('Past Year', 'ultimate-post-kit'), 'exact' => __('Custom', 'ultimate-post-kit'), ], 'condition' => [ 'posts_source!' => 'current_query', ] ] ); $this->add_control( 'posts_date_before', [ 'label' => __('Before', 'ultimate-post-kit'), 'type' => Controls_Manager::DATE_TIME, 'description' => __('Setting a ‘Before’ date will show all the posts published until the chosen date (inclusive).', 'ultimate-post-kit'), 'condition' => [ 'posts_select_date' => 'exact', 'posts_source!' => 'current_query', ] ] ); $this->add_control( 'posts_date_after', [ 'label' => __('After', 'ultimate-post-kit'), 'type' => Controls_Manager::DATE_TIME, 'description' => __('Setting an ‘After’ date will show all the posts published since the chosen date (inclusive).', 'ultimate-post-kit'), 'condition' => [ 'posts_select_date' => 'exact', 'posts_source!' => 'current_query', ] ] ); $this->add_control( 'posts_orderby', [ 'label' => __('Order By', 'ultimate-post-kit'), 'type' => Controls_Manager::SELECT, 'default' => 'date', 'options' => [ 'title' => __('Title', 'ultimate-post-kit'), 'ID' => __('ID', 'ultimate-post-kit'), 'date' => __('Date', 'ultimate-post-kit'), 'author' => __('Author', 'ultimate-post-kit'), 'comment_count' => __('Comment Count', 'ultimate-post-kit'), 'menu_order' => __('Menu Order', 'ultimate-post-kit'), 'modified' => __( 'Last Modified', 'ultimate-post-kit' ), 'rand' => __('Random', 'ultimate-post-kit'), ], ] ); $this->add_control( 'posts_order', [ 'label' => __('Order', 'ultimate-post-kit'), 'type' => Controls_Manager::SELECT, 'default' => 'desc', 'options' => [ 'asc' => __('ASC', 'ultimate-post-kit'), 'desc' => __('DESC', 'ultimate-post-kit'), ], ] ); $this->add_control( 'posts_ignore_sticky_posts', [ 'label' => __('Ignore Sticky Posts', 'ultimate-post-kit'), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => 'yes', 'condition' => [ 'posts_source' => ['post', 'current_query'], ] ] ); $this->add_control( 'posts_only_with_featured_image', [ 'label' => __('Only Featured Image Post', 'ultimate-post-kit'), 'description' => __('Enable to display posts only when featured image is present.', 'ultimate-post-kit'), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'yes', 'condition' => [ 'posts_source!' => 'current_query', ] ] ); $this->add_control( 'query_id', [ 'label' => __('Query ID', 'ultimate-post-kit'), 'description' => __('Give your Query a custom unique id to allow server side filtering', 'ultimate-post-kit'), 'type' => Controls_Manager::TEXT, 'separator' => 'before', ] ); } private function setMetaQueryArgs() { $args = []; if ('current_query' === $this->getGroupControlQueryPostType()) { return []; } $args['order'] = $this->get_settings_for_display('posts_order'); $args['orderby'] = $this->get_settings_for_display('posts_orderby'); /** * Set Feature Images */ if ($this->get_settings_for_display('posts_only_with_featured_image') === 'yes') { // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Elementor widget query built from user-configured controls; expected behaviour. $args['meta_key'] = '_thumbnail_id'; } /** * Set Date */ $selected_date = $this->get_settings_for_display('posts_select_date'); if (!empty($selected_date)) { $date_query = []; switch ($selected_date) { case 'today': $date_query['after'] = '-1 day'; break; case 'week': $date_query['after'] = '-1 week'; break; case 'month': $date_query['after'] = '-1 month'; break; case 'quarter': $date_query['after'] = '-3 month'; break; case 'year': $date_query['after'] = '-1 year'; break; case 'exact': $after_date = $this->get_settings_for_display('posts_date_after'); if (!empty($after_date)) { $date_query['after'] = $after_date; } $before_date = $this->get_settings_for_display('posts_date_before'); if (!empty($before_date)) { $date_query['before'] = $before_date; } $date_query['inclusive'] = true; break; } if (!empty($date_query)) { $args['date_query'] = $date_query; } } return $args; } protected function getGroupControlQueryArgs() { $settings = $this->get_settings_for_display(); $args = $this->setMetaQueryArgs(); $args['post_status'] = 'publish'; $args['suppress_filters'] = false; $exclude_by = $this->getGroupControlQueryParamBy('exclude'); if (0 < $settings['posts_offset']) { $args['offset_to_fix'] = $settings['posts_offset']; } /** * Set Ignore Sticky */ if ( $this->getGroupControlQueryPostType() === 'post' && $this->get_settings_for_display('posts_ignore_sticky_posts') === 'yes' ) { $args['ignore_sticky_posts'] = true; if (in_array('current_post', $exclude_by)) { // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in -- Elementor widget query built from user-configured controls; expected behaviour. $args['post__not_in'] = [get_the_ID()]; } } if ($this->getGroupControlQueryPostType() === 'manual_selection') { /** * Set Including Manually */ $selected_ids = $this->get_settings_for_display('posts_selected_ids'); $selected_ids = wp_parse_id_list($selected_ids); $args['post_type'] = 'any'; if (!empty($selected_ids)) { $args['post__in'] = $selected_ids; } $args['ignore_sticky_posts'] = 1; } elseif ('current_query' === $this->getGroupControlQueryPostType()) { /** * Make Current Query */ $args = $GLOBALS['wp_query']->query_vars; $args = apply_filters('ultimate_post_kit/query/get_query_args/current_query', $args); } elseif ('_ultimate_post_kit_pro_related_post_type' == $this->getGroupControlQueryPostType()) { /** * Set Related Query */ $post_id = get_queried_object_id(); $related_post_id = is_singular() && (0 !== $post_id) ? $post_id : null; $args['post_type'] = get_post_type($related_post_id); $exclude_by = $this->getGroupControlQueryParamBy('exclude'); if (in_array('current_post', $exclude_by)) { // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in -- Elementor widget query built from user-configured controls; expected behaviour. $args['post__not_in'] = [get_the_ID()]; } /** * Set Authors */ $args = $this->getAuthorArgs($args, $settings, $related_post_id); /** * Set Taxonomy */ $args = $this->getTermsArgs($args, $settings); $args['ignore_sticky_posts'] = 1; $args = apply_filters('ultimate_post_kit/query/get_query_args/related_query', $args); } else { /** * Set Post Type */ $args['post_type'] = $this->getGroupControlQueryPostType(); /** * Set Exclude Post */ $exclude_by = $this->getGroupControlQueryParamBy('exclude'); $current_post = []; if (in_array('current_post', $exclude_by) && is_singular()) { $current_post = [get_the_ID()]; } if (in_array('manual_selection', $exclude_by)) { $exclude_ids = $settings['posts_exclude_ids']; // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in -- Elementor widget query built from user-configured controls; expected behaviour. $args['post__not_in'] = array_merge($current_post, wp_parse_id_list($exclude_ids)); } /** * Set Authors */ $args = $this->getAuthorArgs($args, $settings); /** * Set Taxonomy */ $args = $this->getTermsArgs($args, $settings); } if ($this->get_settings_for_display('query_id')) { $args['upk_widget_query'] = true; add_action('pre_get_posts', [$this, 'pre_get_posts_query_filter']); } // fixing custom offset ## https://codex.wordpress.org/Making_Custom_Queries_using_Offset_and_Pagination // Only register the fixers when this widget actually uses an offset, and // scope them to this widget's own query so they never affect other // queries (e.g. another post widget rendered later on the same page). if (!empty($args['offset_to_fix'])) { add_action('pre_get_posts', [$this, 'fix_query'], 1); add_filter('found_posts', [$this, 'prefix_adjust_offset_pagination'], 1, 2); } return $args; } private function getAuthorArgs($args, $settings, mixed $post = null) { $include_by = $this->getGroupControlQueryParamBy('include'); $exclude_by = $this->getGroupControlQueryParamBy('exclude'); $include_users = []; $exclude_users = []; if (in_array('authors', $include_by)) { $include_users = wp_parse_id_list($settings['posts_include_author_ids']); } elseif ($post) { $include_users = get_post_field('post_author', $post); } if (in_array('authors', $exclude_by)) { $exclude_users = wp_parse_id_list($settings['posts_exclude_author_ids']); $include_users = array_diff($include_users, $exclude_users); } if (!empty($include_users)) { $args['author__in'] = $include_users; } if (!empty($exclude_users)) { $args['author__not_in'] = $exclude_users; } return $args; } private function getTermsArgs($args, $settings) { $include_by = $this->getGroupControlQueryParamBy('include'); $exclude_by = $this->getGroupControlQueryParamBy('exclude'); $include_terms = []; $terms_query = []; if (in_array('terms', $include_by)) { $include_terms = wp_parse_id_list($settings['posts_include_term_ids']); } if (in_array('terms', $exclude_by)) { $exclude_terms = wp_parse_id_list($settings['posts_exclude_term_ids']); $include_terms = array_diff($include_terms, $exclude_terms); } if (!empty($include_terms)) { $tax_terms_map = $this->mapGroupControlQuery($include_terms); foreach ($tax_terms_map as $tax => $terms) { $terms_query[] = [ 'taxonomy' => $tax, 'field' => 'term_id', 'terms' => $terms, 'operator' => 'IN', ]; } } if (!empty($exclude_terms)) { $tax_terms_map = $this->mapGroupControlQuery($exclude_terms); foreach ($tax_terms_map as $tax => $terms) { $terms_query[] = [ 'taxonomy' => $tax, 'field' => 'term_id', 'terms' => $terms, 'operator' => 'NOT IN', ]; } } if (!empty($terms_query)) { // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- Elementor widget query built from user-configured controls; expected behaviour. $args['tax_query'] = $terms_query; $args['tax_query']['relation'] = 'AND'; } return $args; } /** * @return mixed */ private function getGroupControlQueryPostType() { return $this->get_settings_for_display('posts_source'); } /** * Get Query Params by args * * @param string $by * * @return array|mixed */ private function getGroupControlQueryParamBy($by = 'exclude') { $mapBy = [ // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude -- Elementor widget query built from user-configured controls; expected behaviour. 'exclude' => 'posts_exclude_by', 'include' => 'posts_include_by', ]; $setting = $this->get_settings_for_display($mapBy[$by]); return (!empty($setting) ? $setting : []); } /** * @param array $term_ids * * @return array */ private function mapGroupControlQuery($term_ids = []) { $terms = get_terms( [ 'term_taxonomy_id' => $term_ids, 'hide_empty' => false, ] ); $tax_terms_map = []; foreach ($terms as $term) { $taxonomy = $term->taxonomy; $tax_terms_map[$taxonomy][] = $term->term_id; } return $tax_terms_map; } /** * @return array|string[]|\WP_Post_Type[] */ private function getGroupControlQueryPostTypes() { $post_types = get_post_types(['public' => true], 'objects'); $post_types = array_column($post_types, 'label', 'name'); $ignorePostTypes = [ 'elementor_library' => '', 'attachment' => '', 'bdt_template_manager' => '', 'bdt-custom-template' => '', ]; $post_types = array_diff_key($post_types, $ignorePostTypes); $extra_types = [ 'manual_selection' => __('Manual Selection', 'ultimate-post-kit'), 'current_query' => __('Current Query', 'ultimate-post-kit'), '_ultimate_post_kit_pro_related_post_type' => __('Related', 'ultimate-post-kit'), ]; $post_types = array_merge($post_types, $extra_types); return $post_types; } /** * @param WP_Query $query fix the offset */ function fix_query( &$query ) { // Only touch this widget's own query. The flag travels with the query // args, so unrelated queries (other widgets, the main loop, etc.) are // left untouched even if this callback is still attached. if ( ! isset( $query->query_vars['offset_to_fix'] ) ) { return; } // Self-remove so this only applies to a single query and never leaks // into subsequent queries on the same request. remove_action( 'pre_get_posts', [ $this, 'fix_query' ], 1 ); $offset = (int) $query->query_vars['offset_to_fix']; $posts_per_page = isset( $query->query_vars['posts_per_page'] ) ? (int) $query->query_vars['posts_per_page'] : (int) get_option( 'posts_per_page', 10 ); if ( $query->is_paged ) { $page_offset = $offset + ( ( $query->query_vars['paged'] - 1 ) * $posts_per_page ); $query->set( 'offset', $page_offset ); } else { $query->set( 'offset', $offset ); } } public function prefix_adjust_offset_pagination( $found_posts, $query ) { if ( ! isset( $query->query_vars['offset_to_fix'] ) ) { return $found_posts; } remove_filter( 'found_posts', [ $this, 'prefix_adjust_offset_pagination' ], 1 ); $offset_to_fix = intval( $query->query_vars['offset_to_fix'] ); if ( $offset_to_fix ) { $found_posts -= $offset_to_fix; } return $found_posts; } public function pre_get_posts_query_filter( $wp_query ) { if ( empty( $wp_query->get( 'upk_widget_query' ) ) ) { return; } remove_action( 'pre_get_posts', [ $this, 'pre_get_posts_query_filter' ] ); $query_id = $this->get_settings_for_display( 'query_id' ); if ( $query_id ) { do_action( "ultimate_post_kit_pro/query/{$query_id}", $wp_query, $this ); } } }