⭐ ' . esc_html__( 'Go Pro', 'webcasata-filterx' ) . '', 'manage_options', 'webcasata-filterx-pro', array( __CLASS__, 'page_pro' ) ); } /* ------------------------------------------------------------------ */ /* Ajax: get taxonomies for a post type */ /* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */ /* Shared: ACF-aware taxonomy lookup for a post type */ /* ------------------------------------------------------------------ */ /** * Return all taxonomy objects associated with a post type, * including those registered by ACF which are missed by * get_object_taxonomies() alone. * * @param string $post_type Post type slug. * @param array $selected Previously-saved taxonomy slugs (always included). * @return array[] Each: [ 'name', 'label', 'checked' ] */ private static function get_taxonomies_for_post_type( $post_type, $selected = array() ) { $skip = array( 'nav_menu', 'link_category', 'post_format', 'wp_theme', 'wp_template_part_area' ); // --- Build slug list --- // 1. Native WP registration $pt_taxonomies = (array) get_object_taxonomies( $post_type, 'names' ); // 2. Walk $wp_taxonomies for any registration that lists this post type global $wp_taxonomies; if ( is_array( $wp_taxonomies ) ) { foreach ( $wp_taxonomies as $slug => $obj ) { if ( isset( $obj->object_type ) && in_array( $post_type, (array) $obj->object_type, true ) ) { $pt_taxonomies[] = $slug; } } } // 3. ACF taxonomy config (handles ACF-registered taxonomies) if ( function_exists( 'acf_get_taxonomies' ) ) { // ACF Pro 6.x $acf_taxes = acf_get_taxonomies(); if ( is_array( $acf_taxes ) ) { foreach ( $acf_taxes as $acf_tax ) { $types = $acf_tax['object_type'] ?? ( $acf_tax['post_types'] ?? array() ); if ( in_array( $post_type, (array) $types, true ) ) { $pt_taxonomies[] = sanitize_key( $acf_tax['taxonomy'] ?? $acf_tax['key'] ?? '' ); } } } } else { // ACF Free / older: read from acf-taxonomy CPT meta $acf_posts = get_posts( array( 'post_type' => 'acf-taxonomy', 'posts_per_page' => -1, 'post_status' => 'publish', 'fields' => 'ids', ) ); foreach ( $acf_posts as $acf_id ) { $config = get_post_meta( $acf_id, 'taxonomy', true ); $tax_slug = ''; $tax_types = array(); if ( is_array( $config ) ) { $tax_slug = $config['taxonomy'] ?? ( $config['key'] ?? '' ); $tax_types = $config['object_type'] ?? ( $config['post_types'] ?? array() ); } // Fallback: ACF stores the slug in the post name / title if ( ! $tax_slug ) { $acf_post = get_post( $acf_id ); $tax_slug = $acf_post ? $acf_post->post_name : ''; // And read object types from key-value post meta $raw_types = get_post_meta( $acf_id, 'object_type', true ); if ( ! $raw_types ) { $raw_types = get_post_meta( $acf_id, 'post_types', true ); } if ( is_string( $raw_types ) ) { $tax_types = explode( ',', $raw_types ); } elseif ( is_array( $raw_types ) ) { $tax_types = $raw_types; } } if ( $tax_slug && in_array( $post_type, array_map( 'trim', (array) $tax_types ), true ) ) { $pt_taxonomies[] = sanitize_key( $tax_slug ); } } } // Deduplicate and clean $pt_taxonomies = array_unique( array_filter( $pt_taxonomies ) ); // Allow theme/plugin developers to modify the list $pt_taxonomies = apply_filters( 'filterx_post_type_taxonomies', $pt_taxonomies, $post_type ); // --- Build output array --- $all_taxonomies = get_taxonomies( array(), 'objects' ); $output = array(); foreach ( $all_taxonomies as $tax ) { if ( in_array( $tax->name, $skip, true ) ) { continue; } $belongs = in_array( $tax->name, $pt_taxonomies, true ); $was_saved = in_array( $tax->name, (array) $selected, true ); if ( ! $belongs && ! $was_saved ) { continue; } $output[] = array( 'name' => $tax->name, 'label' => $tax->labels->name, 'checked' => $was_saved, ); } return $output; } public static function ajax_get_taxonomies() { check_ajax_referer( 'filterx_admin_nonce', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( 'Permission denied', 403 ); } $post_type = sanitize_key( $_POST['post_type'] ?? '' ); $selected = isset( $_POST['selected'] ) ? array_map( 'sanitize_key', (array) $_POST['selected'] ) : array(); $output = self::get_taxonomies_for_post_type( $post_type, $selected ); wp_send_json_success( $output ); } /* ------------------------------------------------------------------ */ /* Dashboard */ /* ------------------------------------------------------------------ */ public static function page_dashboard() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have permission to access this page.', 'webcasata-filterx' ) ); } $items = FilterX_Post_Type::get_all_items(); $notice = ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only notice display if ( isset( $_GET['filterx_saved'] ) ) { $notice = '

' . esc_html__( 'Search item saved.', 'webcasata-filterx' ) . '

'; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only notice display if ( isset( $_GET['filterx_deleted'] ) ) { $notice = '

' . esc_html__( 'Search item deleted.', 'webcasata-filterx' ) . '

'; } ?>

ID ); $shortcode = FilterX_Post_Type::get_shortcode( $item->ID ); $edit_url = admin_url( 'admin.php?page=filterx-new&filterx_id=' . $item->ID ); $del_nonce = wp_create_nonce( 'filterx_delete_item_' . $item->ID ); $del_url = admin_url( 'admin-post.php?action=filterx_delete_item&filterx_id=' . $item->ID . '&_wpnonce=' . $del_nonce ); ?>
post_title ); ?> ' . esc_html( $tax ) . ' '; endforeach; else : echo '—'; endif; ?>
post_title : ''; $is_edit = $post_id > 0; $post_types = get_post_types( array( 'public' => true ), 'objects' ); // For edit mode: pre-load taxonomies for the saved post type. // Uses the same comprehensive ACF-aware lookup as ajax_get_taxonomies(). $existing_taxonomies = array(); if ( $is_edit && ! empty( $settings['post_type'] ) ) { $existing_taxonomies = self::get_taxonomies_for_post_type( $settings['post_type'], $settings['taxonomies'] ); } $pagination_options = array( 'numbers' => array( 'label' => __( 'Numbers', 'webcasata-filterx' ), 'pro' => false, 'desc' => __( 'Standard numbered page links', 'webcasata-filterx' ), 'icon' => '⋯' ), 'load_more' => array( 'label' => __( 'Load More', 'webcasata-filterx' ), 'pro' => false, 'desc' => __( 'Button to append the next batch of posts', 'webcasata-filterx' ), 'icon' => '↓' ), 'infinite' => array( 'label' => __( 'Infinite Scroll', 'webcasata-filterx' ),'pro' => true, 'desc' => __( 'Auto-load posts as user scrolls', 'webcasata-filterx' ), 'icon' => '∞' ), 'hybrid' => array( 'label' => __( 'Hybrid Scroll', 'webcasata-filterx' ), 'pro' => true, 'desc' => __( 'Infinite scroll + Load More combined', 'webcasata-filterx' ), 'icon' => '⇅' ), ); $is_pro = apply_filters( 'filterx_is_pro', false ); $form_action = admin_url( 'admin-post.php' ); $nonce = wp_create_nonce( 'filterx_save_item' ); $shortcode = $is_edit ? FilterX_Post_Type::get_shortcode( $post_id ) : ''; ?>
✓
1

2

name ); ?>
3

4

'Grid', 'list' => 'List', 'masonry' => 'Masonry' ) as $val => $lbl ) : ?>
__( 'Title', 'webcasata-filterx' ), 'show_image' => __( 'Image', 'webcasata-filterx' ), 'show_excerpt' => __( 'Excerpt', 'webcasata-filterx' ), 'show_meta' => __( 'Meta', 'webcasata-filterx' ), 'show_terms' => __( 'Terms', 'webcasata-filterx' ), 'show_button' => __( 'Button', 'webcasata-filterx' ), ); foreach ( $card_toggles as $field => $label ) : $checked = isset( $settings[ $field ] ) ? (bool) $settings[ $field ] : true; ?>

5

$opt ) : $is_locked = $opt['pro'] && ! $is_pro; $is_selected = $settings['pagination_type'] === $key; ?>
6

⭐

' . esc_html__( 'Settings saved.', 'webcasata-filterx' ) . '

'; } ?>

$t['overridden'] ) ); $theme_dir_exists = is_dir( $theme_dir_abs ); $debug_url = add_query_arg( 'filterx_debug_templates', '1', home_url( '/' ) ); ?>

/webcasata-filterx/loop/card.php

✓

✓ Yes' ) : wp_kses_post( '✗ Not found' ); ?>

⭐ Your theme' ) : wp_kses_post( 'Plugin default' ); ?>
⭐

'Security check failed.' ), 403 ); } $search_id = absint( $_POST['search_id'] ?? 0 ); if ( ! $search_id ) { wp_send_json_error( array( 'message' => 'Invalid search ID.' ), 400 ); } $settings = FilterX_Post_Type::get_settings( $search_id ); $search_query = sanitize_text_field( wp_unslash( $_POST['search_query'] ?? '' ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- sanitized key-by-key below $active_tax_raw = isset( $_POST['tax_query'] ) ? (array) wp_unslash( $_POST['tax_query'] ) : array(); $active_filters = array(); // taxonomy => term_slug foreach ( $active_tax_raw as $tax => $slug ) { $tax = sanitize_key( $tax ); $slug = sanitize_text_field( $slug ); if ( $tax && $slug ) { $active_filters[ $tax ] = $slug; } } $counts = array(); // taxonomy => [ term_slug => count ] foreach ( $settings['taxonomies'] as $taxonomy ) { $taxonomy = sanitize_key( $taxonomy ); $terms = get_terms( array( 'taxonomy' => $taxonomy, 'hide_empty' => true ) ); if ( is_wp_error( $terms ) || empty( $terms ) ) { $counts[ $taxonomy ] = array(); continue; } $term_counts = array(); foreach ( $terms as $term ) { // Build query args with all OTHER filters active, then check count for this term $args = array( 'post_type' => $settings['post_type'], 'post_status' => 'publish', 'posts_per_page' => 1, 'no_found_rows' => false, 'fields' => 'ids', ); if ( ! empty( $search_query ) ) { $args['s'] = $search_query; } // Build tax_query: all active filters EXCEPT this taxonomy + this term $tax_query_parts = array( 'relation' => 'AND' ); foreach ( $active_filters as $active_tax => $active_slug ) { if ( $active_tax === $taxonomy ) { continue; // skip self — we're testing each term of THIS taxonomy } $tax_query_parts[] = array( 'taxonomy' => $active_tax, 'field' => 'slug', 'terms' => array( $active_slug ), 'operator' => 'IN', ); } // Add this specific term $tax_query_parts[] = array( 'taxonomy' => $taxonomy, 'field' => 'slug', 'terms' => array( $term->slug ), 'operator' => 'IN', ); if ( count( $tax_query_parts ) > 1 ) { $args['tax_query'] = $tax_query_parts; } $args = apply_filters( 'filterx_query_args', $args, $settings ); $q = new WP_Query( $args ); $term_counts[ $term->slug ] = (int) $q->found_posts; } $counts[ $taxonomy ] = $term_counts; } wp_send_json_success( array( 'counts' => $counts ) ); } public static function handle_save_settings() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'Permission denied.', 'webcasata-filterx' ) ); } if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), 'filterx_save_settings' ) ) { wp_die( esc_html__( 'Security check failed.', 'webcasata-filterx' ) ); } $opts = array( 'default_columns' => min( 6, max( 1, absint( $_POST['default_columns'] ?? 3 ) ) ), 'default_posts_per_page' => min( 100, max( 1, absint( $_POST['default_posts_per_page'] ?? 6 ) ) ), 'card_css_class' => sanitize_html_class( wp_unslash( $_POST['card_css_class'] ?? '' ) ), 'date_format' => sanitize_text_field( wp_unslash( $_POST['date_format'] ?? '' ) ), 'load_acf_style' => ! empty( $_POST['load_acf_style'] ), 'show_sort_order' => ! empty( $_POST['show_sort_order'] ), 'show_filter_tabs' => ! empty( $_POST['show_filter_tabs'] ), 'show_term_count' => ! empty( $_POST['show_term_count'] ), 'smart_filters' => ! empty( $_POST['smart_filters'] ), ); update_option( 'webcasata_filterx_global_settings', $opts ); wp_safe_redirect( admin_url( 'admin.php?page=filterx-settings&filterx_settings_saved=1' ) ); exit; } }