PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.87
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.87
51.1.87 51.1.86 51.1.84 51.1.85 51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 All 41 releases
← All changes | includes/helpers/Dynamic_Posts_Grid_Ajax.php +160 -22 51.1.14 → 51.1.87 View file →
@@ -15,13 +15,33 @@
15 15 * for the Dynamic Posts Grid widget.
16 16 */
17 17 class Dynamic_Posts_Grid_Ajax
18 18 {
19 + private static ?Dynamic_Posts_Grid_Ajax $_instance = null;
20 + private static bool $initialized = false;
21 +
19 22 /**
23 + * Get singleton instance.
24 + */
25 + public static function get_instance(): Dynamic_Posts_Grid_Ajax
26 + {
27 + if (is_null(self::$_instance)) {
28 + self::$_instance = new self();
29 + }
30 + return self::$_instance;
31 + }
32 +
33 + /**
20 34 * Constructor.
21 35 */
22 36 public function __construct()
23 37 {
38 + // Prevent double initialization
39 + if (self::$initialized) {
40 + return;
41 + }
42 + self::$initialized = true;
43 +
24 44 $this->init_hooks();
25 45 }
26 46
27 47 /**
@@ -55,17 +75,35 @@
55 75 * Handle filter AJAX request.
56 76 */
57 77 public function handle_filter_request(): void
58 78 {
59 - // Verify nonce
60 - if (!wp_verify_nonce($_POST['nonce'] ?? '', 'king_addons_dynamic_posts_grid_nonce')) {
61 - wp_send_json_error(['message' => esc_html__('Security check failed.', 'king-addons')]);
62 - }
79 + try {
80 + // Verify nonce
81 + if (!wp_verify_nonce($_POST['nonce'] ?? '', 'king_addons_dynamic_posts_grid_nonce')) {
82 + wp_send_json_error(['message' => esc_html__('Security check failed.', 'king-addons')]);
83 + }
63 84
64 85 // Get and sanitize POST data
65 86 $widget_id = sanitize_text_field($_POST['widget_id'] ?? '');
66 87 $posts_per_page = absint($_POST['posts_per_page'] ?? 12);
67 - $post_types = $this->sanitize_array($_POST['post_types'] ?? ['post']);
88 + // Handle post_types - it comes as JSON string from JavaScript or as array
89 + $post_types_raw = $_POST['post_types'] ?? '["post"]';
90 +
91 + // Debug: log the type and value of post_types_raw
92 + // error_log('DEBUG: post_types_raw type: ' . gettype($post_types_raw) . ', value: ' . print_r($post_types_raw, true));
93 +
94 + // Check if it's already an array (PRO version) or needs decoding (Free version)
95 + if (is_array($post_types_raw)) {
96 + $post_types = $this->sanitize_array($post_types_raw);
97 + } else {
98 + // Ensure it's a string before decoding
99 + if (!is_string($post_types_raw)) {
100 + // error_log('DEBUG: post_types_raw is not a string, converting to: ' . json_encode($post_types_raw));
101 + $post_types_raw = json_encode($post_types_raw);
102 + }
103 + $post_types_decoded = json_decode($post_types_raw, true);
104 + $post_types = is_array($post_types_decoded) ? $this->sanitize_array($post_types_decoded) : ['post'];
105 + }
68 106 $orderby = sanitize_text_field($_POST['orderby'] ?? 'date');
69 107 $order = sanitize_text_field($_POST['order'] ?? 'DESC');
70 108 $filter_taxonomy = sanitize_text_field($_POST['filter_taxonomy'] ?? 'category');
71 109 $filter_term = sanitize_text_field($_POST['filter_term'] ?? '*');
@@ -70,8 +108,10 @@
70 108 $filter_taxonomy = sanitize_text_field($_POST['filter_taxonomy'] ?? 'category');
71 109 $filter_term = sanitize_text_field($_POST['filter_term'] ?? '*');
72 110 $search_query = sanitize_text_field($_POST['search_query'] ?? '');
73 111 $page = absint($_POST['page'] ?? 1);
112 + $cpt_actions_raw = wp_unslash($_POST['cpt_actions'] ?? '');
113 + $cpt_actions = $this->sanitize_cpt_actions($cpt_actions_raw);
74 114 $show_excerpt = ($_POST['show_excerpt'] ?? '1') === '1';
75 115
76 116 // Build query arguments
77 117 $query_args = $this->build_query_args([
@@ -87,10 +127,11 @@
87 127
88 128 // Execute query
89 129 $posts_query = new \WP_Query($query_args);
90 130
91 - // Generate posts HTML
92 - $posts_html = $this->generate_posts_html($posts_query, $show_excerpt);
131 + // Generate posts HTML (use CPT classing when filtering by post_type)
132 + $is_cpt_mode = ($filter_taxonomy === 'post_type');
133 + $posts_html = $this->generate_posts_html($posts_query, $show_excerpt, $is_cpt_mode, $cpt_actions);
93 134
94 135 // Prepare response data
95 136 $response_data = [
96 137 'posts_html' => $posts_html,
@@ -100,8 +141,13 @@
100 141 'current_count' => $posts_query->post_count,
101 142 ];
102 143
103 144 wp_send_json_success($response_data);
145 + } catch (Exception $e) {
146 + // error_log('Dynamic Posts Grid Filter AJAX Error: ' . $e->getMessage());
147 + // error_log('Stack trace: ' . $e->getTraceAsString());
148 + wp_send_json_error(['message' => esc_html__('An error occurred while filtering posts.', 'king-addons')]);
149 + }
104 150 }
105 151
106 152 /**
107 153 * Handle load more AJAX request.
@@ -107,17 +153,35 @@
107 153 * Handle load more AJAX request.
108 154 */
109 155 public function handle_load_more_request(): void
110 156 {
111 - // Verify nonce
112 - if (!wp_verify_nonce($_POST['nonce'] ?? '', 'king_addons_dynamic_posts_grid_nonce')) {
113 - wp_send_json_error(['message' => esc_html__('Security check failed.', 'king-addons')]);
114 - }
157 + try {
158 + // Verify nonce
159 + if (!wp_verify_nonce($_POST['nonce'] ?? '', 'king_addons_dynamic_posts_grid_nonce')) {
160 + wp_send_json_error(['message' => esc_html__('Security check failed.', 'king-addons')]);
161 + }
115 162
116 163 // Get and sanitize POST data
117 164 $widget_id = sanitize_text_field($_POST['widget_id'] ?? '');
118 165 $posts_per_page = absint($_POST['posts_per_page'] ?? 12);
119 - $post_types = $this->sanitize_array($_POST['post_types'] ?? ['post']);
166 + // Handle post_types - it comes as JSON string from JavaScript or as array
167 + $post_types_raw = $_POST['post_types'] ?? '["post"]';
168 +
169 + // Debug: log the type and value of post_types_raw
170 + // error_log('DEBUG: post_types_raw type: ' . gettype($post_types_raw) . ', value: ' . print_r($post_types_raw, true));
171 +
172 + // Check if it's already an array (PRO version) or needs decoding (Free version)
173 + if (is_array($post_types_raw)) {
174 + $post_types = $this->sanitize_array($post_types_raw);
175 + } else {
176 + // Ensure it's a string before decoding
177 + if (!is_string($post_types_raw)) {
178 + // error_log('DEBUG: post_types_raw is not a string, converting to: ' . json_encode($post_types_raw));
179 + $post_types_raw = json_encode($post_types_raw);
180 + }
181 + $post_types_decoded = json_decode($post_types_raw, true);
182 + $post_types = is_array($post_types_decoded) ? $this->sanitize_array($post_types_decoded) : ['post'];
183 + }
120 184 $orderby = sanitize_text_field($_POST['orderby'] ?? 'date');
121 185 $order = sanitize_text_field($_POST['order'] ?? 'DESC');
122 186 $filter_taxonomy = sanitize_text_field($_POST['filter_taxonomy'] ?? 'category');
123 187 $filter_term = sanitize_text_field($_POST['filter_term'] ?? '*');
@@ -122,8 +186,10 @@
122 186 $filter_taxonomy = sanitize_text_field($_POST['filter_taxonomy'] ?? 'category');
123 187 $filter_term = sanitize_text_field($_POST['filter_term'] ?? '*');
124 188 $search_query = sanitize_text_field($_POST['search_query'] ?? '');
125 189 $page = absint($_POST['page'] ?? 1);
190 + $cpt_actions_raw = wp_unslash($_POST['cpt_actions'] ?? '');
191 + $cpt_actions = $this->sanitize_cpt_actions($cpt_actions_raw);
126 192 $show_excerpt = ($_POST['show_excerpt'] ?? '1') === '1';
127 193
128 194 // Build query arguments for load more (accumulative)
129 195 $query_args = $this->build_query_args([
@@ -139,10 +205,11 @@
139 205
140 206 // Execute query
141 207 $posts_query = new \WP_Query($query_args);
142 208
143 - // Generate posts HTML
144 - $posts_html = $this->generate_posts_html($posts_query, $show_excerpt);
209 + // Generate posts HTML (use CPT classing when filtering by post_type)
210 + $is_cpt_mode = ($filter_taxonomy === 'post_type');
211 + $posts_html = $this->generate_posts_html($posts_query, $show_excerpt, $is_cpt_mode, $cpt_actions);
145 212
146 213 // Calculate total shown posts (previous pages + current page)
147 214 $total_shown = (($page - 1) * $posts_per_page) + $posts_query->post_count;
148 215
@@ -155,8 +222,13 @@
155 222 'current_count' => $total_shown,
156 223 ];
157 224
158 225 wp_send_json_success($response_data);
226 + } catch (Exception $e) {
227 + // error_log('Dynamic Posts Grid AJAX Error: ' . $e->getMessage());
228 + // error_log('Stack trace: ' . $e->getTraceAsString());
229 + wp_send_json_error(['message' => esc_html__('An error occurred while loading posts.', 'king-addons')]);
230 + }
159 231 }
160 232
161 233 /**
162 234 * Build WP_Query arguments.
@@ -207,9 +279,9 @@
207 279 * @param \WP_Query $posts_query WP_Query object.
208 280 * @param bool $show_excerpt Whether to show excerpt.
209 281 * @return string Generated HTML.
210 282 */
211 - private function generate_posts_html(\WP_Query $posts_query, bool $show_excerpt = true): string
283 + private function generate_posts_html(\WP_Query $posts_query, bool $show_excerpt = true, bool $is_cpt_mode = false, array $cpt_actions = []): string
212 284 {
213 285 if (!$posts_query->have_posts()) {
214 286 return '<div class="king-addons-dpg-no-posts">' . esc_html__('No posts found.', 'king-addons') . '</div>';
215 287 }
@@ -218,15 +290,24 @@
218 290
219 291 while ($posts_query->have_posts()):
220 292 $posts_query->the_post();
221 293 $post = get_post();
222 - $post_type_display = $this->get_post_type_display($post);
223 - $category_classes = $this->get_post_category_color_classes($post);
224 294 $cta_text = $this->get_cta_text($post);
225 - $post_icon = $this->get_post_type_icon($post);
295 +
296 + if ($is_cpt_mode) {
297 + $post_type = $post->post_type;
298 + $card_classes = 'king-addons-dpg-cpt-' . $post_type;
299 + $post_type_obj = get_post_type_object($post_type);
300 + $post_type_display = $post_type_obj ? strtoupper($post_type_obj->label) : strtoupper($post_type);
301 + $post_icon = '<i class="far fa-file-alt"></i>';
302 + } else {
303 + $post_type_display = $this->get_post_type_display($post);
304 + $card_classes = $this->get_post_category_color_classes($post);
305 + $post_icon = $this->get_post_type_icon($post);
306 + }
226 307 ?>
227 308
228 - <div class="king-addons-dpg-card king-addons-dpg-item <?php echo esc_attr($category_classes); ?>" data-post-id="<?php echo esc_attr($post->ID); ?>">
309 + <div class="king-addons-dpg-card king-addons-dpg-item <?php echo esc_attr($card_classes); ?>" data-post-id="<?php echo esc_attr($post->ID); ?>"<?php echo $is_cpt_mode ? ' data-post-type="' . esc_attr($post->post_type) . '"' : ''; ?>>
229 310
230 311 <!-- Post Type Header -->
231 312 <div class="king-addons-dpg-header">
232 313 <div class="king-addons-dpg-icon">
@@ -253,11 +334,38 @@
253 334 </div>
254 335
255 336 <!-- CTA Button -->
256 337 <div class="king-addons-dpg-cta">
257 - <a href="<?php echo esc_url(get_permalink()); ?>" class="king-addons-dpg-button">
258 - <?php echo esc_html($cta_text); ?>
259 - </a>
338 + <?php if ($is_cpt_mode) : ?>
339 + <?php
340 + $pt = $post->post_type;
341 + $action_conf = $cpt_actions[$pt] ?? [];
342 + $url_field = $action_conf['url_field'] ?? '';
343 + $action_type = $action_conf['action_type'] ?? '';
344 + $cta_override = $action_conf['cta_text'] ?? '';
345 + $meta_url = $url_field ? get_post_meta($post->ID, $url_field, true) : '';
346 + ?>
347 + <?php
348 + // Security fix: Block dangerous protocols like javascript:, data:, vbscript:
349 + $is_safe_url = !preg_match('/^(javascript|data|vbscript):/i', $meta_url);
350 + ?>
351 + <?php if (!empty($meta_url) && !empty($action_type) && $is_safe_url) : ?>
352 + <button class="king-addons-dpg-button king-addons-dpg-action-btn"
353 + data-action="<?php echo esc_attr($action_type); ?>"
354 + data-url="<?php echo esc_url($meta_url); ?>"
355 + data-title="<?php echo esc_attr(get_the_title()); ?>">
356 + <?php echo esc_html(strtoupper($cta_override ?: 'VIEW')); ?>
357 + </button>
358 + <?php else: ?>
359 + <a href="<?php echo esc_url(get_permalink()); ?>" class="king-addons-dpg-button">
360 + <?php echo esc_html($cta_text); ?>
361 + </a>
362 + <?php endif; ?>
363 + <?php else: ?>
364 + <a href="<?php echo esc_url(get_permalink()); ?>" class="king-addons-dpg-button">
365 + <?php echo esc_html($cta_text); ?>
366 + </a>
367 + <?php endif; ?>
260 368 </div>
261 369
262 370 </div>
263 371
@@ -411,8 +519,38 @@
411 519 return '<i class="fas fa-globe"></i>';
412 520 } else {
413 521 return '<i class="far fa-file-alt"></i>';
414 522 }
523 + }
524 +
525 + /**
526 + * Sanitize CPT actions configuration JSON.
527 + *
528 + * @param string $raw_json Raw JSON from request.
529 + * @return array Sanitized CPT actions config.
530 + */
531 + private function sanitize_cpt_actions(string $raw_json): array
532 + {
533 + if (empty($raw_json)) {
534 + return [];
535 + }
536 + $decoded = json_decode($raw_json, true);
537 + if (!is_array($decoded)) {
538 + return [];
539 + }
540 + $sanitized = [];
541 + foreach ($decoded as $pt => $conf) {
542 + $pt_key = sanitize_key($pt);
543 + if (!$pt_key) {
544 + continue;
545 + }
546 + $sanitized[$pt_key] = [
547 + 'url_field' => isset($conf['url_field']) ? sanitize_key($conf['url_field']) : '',
548 + 'action_type' => isset($conf['action_type']) ? sanitize_text_field($conf['action_type']) : '',
549 + 'cta_text' => isset($conf['cta_text']) ? sanitize_text_field($conf['cta_text']) : '',
550 + ];
551 + }
552 + return $sanitized;
415 553 }
416 554
417 555 /**
418 556 * Sanitize array values.