init_hooks(); } /** * Initialize hooks. */ private function init_hooks(): void { // AJAX actions for both logged in and non-logged in users add_action('wp_ajax_king_addons_dynamic_posts_grid_filter', [$this, 'handle_filter_request']); add_action('wp_ajax_nopriv_king_addons_dynamic_posts_grid_filter', [$this, 'handle_filter_request']); add_action('wp_ajax_king_addons_dynamic_posts_grid_load_more', [$this, 'handle_load_more_request']); add_action('wp_ajax_nopriv_king_addons_dynamic_posts_grid_load_more', [$this, 'handle_load_more_request']); // Enqueue AJAX variables add_action('wp_enqueue_scripts', [$this, 'enqueue_ajax_variables']); } /** * Enqueue AJAX variables for frontend. */ public function enqueue_ajax_variables(): void { wp_localize_script('jquery', 'KingAddonsDynamicPostsGrid', [ 'ajaxUrl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('king_addons_dynamic_posts_grid_nonce'), ]); } /** * Handle filter AJAX request. */ public function handle_filter_request(): void { try { // Verify nonce if (!wp_verify_nonce($_POST['nonce'] ?? '', 'king_addons_dynamic_posts_grid_nonce')) { wp_send_json_error(['message' => esc_html__('Security check failed.', 'king-addons')]); } // Get and sanitize POST data $widget_id = sanitize_text_field($_POST['widget_id'] ?? ''); $posts_per_page = absint($_POST['posts_per_page'] ?? 12); // Handle post_types - it comes as JSON string from JavaScript or as array $post_types_raw = $_POST['post_types'] ?? '["post"]'; // Debug: log the type and value of post_types_raw // error_log('DEBUG: post_types_raw type: ' . gettype($post_types_raw) . ', value: ' . print_r($post_types_raw, true)); // Check if it's already an array (PRO version) or needs decoding (Free version) if (is_array($post_types_raw)) { $post_types = $this->sanitize_array($post_types_raw); } else { // Ensure it's a string before decoding if (!is_string($post_types_raw)) { // error_log('DEBUG: post_types_raw is not a string, converting to: ' . json_encode($post_types_raw)); $post_types_raw = json_encode($post_types_raw); } $post_types_decoded = json_decode($post_types_raw, true); $post_types = is_array($post_types_decoded) ? $this->sanitize_array($post_types_decoded) : ['post']; } $orderby = sanitize_text_field($_POST['orderby'] ?? 'date'); $order = sanitize_text_field($_POST['order'] ?? 'DESC'); $filter_taxonomy = sanitize_text_field($_POST['filter_taxonomy'] ?? 'category'); $filter_term = sanitize_text_field($_POST['filter_term'] ?? '*'); $search_query = sanitize_text_field($_POST['search_query'] ?? ''); $page = absint($_POST['page'] ?? 1); $cpt_actions_raw = wp_unslash($_POST['cpt_actions'] ?? ''); $cpt_actions = $this->sanitize_cpt_actions($cpt_actions_raw); $show_excerpt = ($_POST['show_excerpt'] ?? '1') === '1'; // Build query arguments $query_args = $this->build_query_args([ 'posts_per_page' => $posts_per_page, 'post_types' => $post_types, 'orderby' => $orderby, 'order' => $order, 'filter_taxonomy' => $filter_taxonomy, 'filter_term' => $filter_term, 'search_query' => $search_query, 'page' => $page, ]); // Execute query $posts_query = new \WP_Query($query_args); // Generate posts HTML (use CPT classing when filtering by post_type) $is_cpt_mode = ($filter_taxonomy === 'post_type'); $posts_html = $this->generate_posts_html($posts_query, $show_excerpt, $is_cpt_mode, $cpt_actions); // Prepare response data $response_data = [ 'posts_html' => $posts_html, 'current_page' => $page, 'max_pages' => $posts_query->max_num_pages, 'total_posts' => $posts_query->found_posts, 'current_count' => $posts_query->post_count, ]; wp_send_json_success($response_data); } catch (Exception $e) { // error_log('Dynamic Posts Grid Filter AJAX Error: ' . $e->getMessage()); // error_log('Stack trace: ' . $e->getTraceAsString()); wp_send_json_error(['message' => esc_html__('An error occurred while filtering posts.', 'king-addons')]); } } /** * Handle load more AJAX request. */ public function handle_load_more_request(): void { try { // Verify nonce if (!wp_verify_nonce($_POST['nonce'] ?? '', 'king_addons_dynamic_posts_grid_nonce')) { wp_send_json_error(['message' => esc_html__('Security check failed.', 'king-addons')]); } // Get and sanitize POST data $widget_id = sanitize_text_field($_POST['widget_id'] ?? ''); $posts_per_page = absint($_POST['posts_per_page'] ?? 12); // Handle post_types - it comes as JSON string from JavaScript or as array $post_types_raw = $_POST['post_types'] ?? '["post"]'; // Debug: log the type and value of post_types_raw // error_log('DEBUG: post_types_raw type: ' . gettype($post_types_raw) . ', value: ' . print_r($post_types_raw, true)); // Check if it's already an array (PRO version) or needs decoding (Free version) if (is_array($post_types_raw)) { $post_types = $this->sanitize_array($post_types_raw); } else { // Ensure it's a string before decoding if (!is_string($post_types_raw)) { // error_log('DEBUG: post_types_raw is not a string, converting to: ' . json_encode($post_types_raw)); $post_types_raw = json_encode($post_types_raw); } $post_types_decoded = json_decode($post_types_raw, true); $post_types = is_array($post_types_decoded) ? $this->sanitize_array($post_types_decoded) : ['post']; } $orderby = sanitize_text_field($_POST['orderby'] ?? 'date'); $order = sanitize_text_field($_POST['order'] ?? 'DESC'); $filter_taxonomy = sanitize_text_field($_POST['filter_taxonomy'] ?? 'category'); $filter_term = sanitize_text_field($_POST['filter_term'] ?? '*'); $search_query = sanitize_text_field($_POST['search_query'] ?? ''); $page = absint($_POST['page'] ?? 1); $cpt_actions_raw = wp_unslash($_POST['cpt_actions'] ?? ''); $cpt_actions = $this->sanitize_cpt_actions($cpt_actions_raw); $show_excerpt = ($_POST['show_excerpt'] ?? '1') === '1'; // Build query arguments for load more (accumulative) $query_args = $this->build_query_args([ 'posts_per_page' => $posts_per_page, 'post_types' => $post_types, 'orderby' => $orderby, 'order' => $order, 'filter_taxonomy' => $filter_taxonomy, 'filter_term' => $filter_term, 'search_query' => $search_query, 'page' => $page, ]); // Execute query $posts_query = new \WP_Query($query_args); // Generate posts HTML (use CPT classing when filtering by post_type) $is_cpt_mode = ($filter_taxonomy === 'post_type'); $posts_html = $this->generate_posts_html($posts_query, $show_excerpt, $is_cpt_mode, $cpt_actions); // Calculate total shown posts (previous pages + current page) $total_shown = (($page - 1) * $posts_per_page) + $posts_query->post_count; // Prepare response data $response_data = [ 'posts_html' => $posts_html, 'current_page' => $page, 'max_pages' => $posts_query->max_num_pages, 'total_posts' => $posts_query->found_posts, 'current_count' => $total_shown, ]; wp_send_json_success($response_data); } catch (Exception $e) { // error_log('Dynamic Posts Grid AJAX Error: ' . $e->getMessage()); // error_log('Stack trace: ' . $e->getTraceAsString()); wp_send_json_error(['message' => esc_html__('An error occurred while loading posts.', 'king-addons')]); } } /** * Build WP_Query arguments. * * @param array $params Query parameters. * @return array WP_Query arguments. */ private function build_query_args(array $params): array { $query_args = [ 'post_type' => $params['post_types'], 'posts_per_page' => $params['posts_per_page'], 'paged' => $params['page'], 'orderby' => $params['orderby'], 'order' => $params['order'], 'post_status' => 'publish', 'ignore_sticky_posts' => true, ]; // Add search query if (!empty($params['search_query'])) { $query_args['s'] = $params['search_query']; } // Add taxonomy filter if (!empty($params['filter_term']) && $params['filter_term'] !== '*') { $query_args['tax_query'] = [ [ 'taxonomy' => $params['filter_taxonomy'], 'field' => 'slug', 'terms' => $params['filter_term'], ] ]; } // Handle random order if ($params['orderby'] === 'rand') { $query_args['orderby'] = 'rand'; unset($query_args['order']); // Order is not applicable for random } return $query_args; } /** * Generate posts HTML. * * @param \WP_Query $posts_query WP_Query object. * @param bool $show_excerpt Whether to show excerpt. * @return string Generated HTML. */ private function generate_posts_html(\WP_Query $posts_query, bool $show_excerpt = true, bool $is_cpt_mode = false, array $cpt_actions = []): string { if (!$posts_query->have_posts()) { return '