PluginProbe
Ultimate Post Kit – Elementor Post Grid, Post Carousel, Post Slider & Blog Layout Widgets / 4.1.11
Ultimate Post Kit – Elementor Post Grid, Post Carousel, Post Slider & Blog Layout Widgets v4.1.11
4.5.4 4.2.1 4.2.2 4.2.3 4.5.0 4.5.2 4.5.3 4.2.0 4.1.18 4.1.17 4.1.16 4.1.15 4.1.14 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.0.9 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 All 146 releases
ultimate-post-kit / includes / setup-wizard / class-remote-data-handler.php

class-remote-data-handler.php in Ultimate Post Kit – Elementor Post Grid, Post Carousel, Post Slider & Blog Layout Widgets 4.1.11, at includes/setup-wizard/class-remote-data-handler.php

585 lines 19.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Ultimate Post Kit Remote Data Handler
4 *
5 * Handles remote API data loading with proper caching and background processing
6 * to prevent blocking admin pages.
7 */
8
9 namespace UltimatePostKit\SetupWizard;
10
11 if (!defined('ABSPATH')) {
12 exit;
13 }
14
15
16
17 class Remote_Data_Handler {
18
19 /**
20 * Cache duration in seconds (7 days)
21 */
22 const CACHE_DURATION = 7 * DAY_IN_SECONDS;
23
24 /**
25 * Transient key for remote plugins data
26 */
27 const CACHE_KEY = 'bdt_remote_plugins_data';
28
29 /**
30 * Cron hook name for background fetch
31 */
32 const CRON_HOOK = 'bdt_fetch_remote_plugins_cron';
33
34 /**
35 * Initialize the remote data handler
36 */
37 public static function init() {
38 add_action('init', [__CLASS__, 'schedule_cron']);
39 add_action(self::CRON_HOOK, [__CLASS__, 'cron_fetch_plugins']);
40 add_action('wp_ajax_upk_get_plugins', [__CLASS__, 'ajax_get_plugins']);
41 add_action('wp_ajax_nopriv_upk_get_plugins', [__CLASS__, 'ajax_get_plugins']);
42 }
43
44 /**
45 * WP-Cron callback for fetching plugins
46 */
47 public static function cron_fetch_plugins() {
48 self::fetch_remote_plugins_now();
49 }
50
51 /**
52 * Check if we're on the Ultimate Post Kit options page
53 *
54 * @return bool True if on Ultimate Post Kit options page
55 */
56 public static function is_element_pack_page() {
57 if (!is_admin()) {
58 return false;
59 }
60
61 // Check if this is an AJAX request for our plugins
62 if (wp_doing_ajax() && isset($_REQUEST['action'])) {
63 $action = sanitize_text_field($_REQUEST['action']);
64 if (in_array($action, ['upk_get_plugins'])) {
65 return true;
66 }
67 }
68
69 $page = isset($_GET['page']) ? sanitize_text_field($_GET['page']) : '';
70 return $page === 'element_pack_options';
71 }
72
73 /**
74 * Get remote plugins data from cache
75 *
76 * @return array Cached plugins data or empty array if not available
77 */
78 public static function get_remote_plugins() {
79 $cached_data = get_transient(self::CACHE_KEY);
80
81 if ($cached_data !== false) {
82 return $cached_data;
83 }
84
85 // If no cache exists, schedule background fetch and return empty array
86 self::schedule_remote_fetch();
87
88 return [];
89 }
90
91 /**
92 * Schedule a background fetch via WP-Cron
93 *
94 * @return bool True if successfully scheduled
95 */
96 public static function schedule_remote_fetch() {
97 // Schedule to run immediately if not already scheduled
98 if (!wp_next_scheduled(self::CRON_HOOK)) {
99 wp_schedule_single_event(time(), self::CRON_HOOK);
100 return true;
101 }
102
103 return false;
104 }
105
106 /**
107 * Fetch remote plugins data immediately (for background processing only)
108 *
109 * @return array|false Plugins data or false on failure
110 */
111 public static function fetch_remote_plugins_now() {
112 // Define plugin slugs to fetch
113 $plugin_slugs = [
114 'bdthemes-element-pack-lite',
115 'bdthemes-prime-slider-lite',
116 'ultimate-post-kit',
117 'ultimate-store-kit',
118 'zoloblocks',
119 'pixel-gallery',
120 'live-copy-paste',
121 'spin-wheel',
122 'ai-image',
123 'dark-reader',
124 'ar-viewer',
125 'smart-admin-assistant',
126 'website-accessibility',
127 ];
128
129 $results = [];
130 $errors = [];
131
132 foreach ($plugin_slugs as $slug) {
133 // Direct API fetch - no external dependencies
134 $data = self::fetch_plugin_from_api($slug);
135 if ($data !== false) {
136 $results[$slug] = $data;
137 } else {
138 $errors[] = $slug;
139 }
140 }
141
142 // Cache the results for 7 days
143 set_transient(self::CACHE_KEY, $results, self::CACHE_DURATION);
144
145 return $results;
146 }
147
148 /**
149 * AJAX handler for getting plugins data
150 */
151 public static function ajax_get_plugins() {
152 // Verify nonce for security
153 if (!check_ajax_referer('upk_get_plugins_nonce', 'nonce', false)) {
154 wp_die(__('Security check failed.', 'ultimate-post-kit'));
155 }
156
157 // Get cached data
158 $plugins_data = self::get_remote_plugins();
159
160 // If cache is empty, fetch immediately for better UX
161 if (empty($plugins_data)) {
162 // Try to fetch data immediately (this is an AJAX request, so it's async already)
163 $plugins_data = self::fetch_remote_plugins_now();
164
165 // If still empty after fetch, schedule background cron for retry
166 if (empty($plugins_data)) {
167 self::schedule_remote_fetch();
168
169 // Return empty response with flag indicating data is loading
170 wp_send_json_success([
171 'plugins' => [],
172 'loading' => true,
173 'message' => __('Loading plugin data...', 'ultimate-post-kit')
174 ]);
175 }
176 }
177
178 // Get recommended flags from Plugin_Integration_Helper (key may be 'slug' or 'slug/script.php')
179 $recommended_by_slug = [];
180 $helper_file = __DIR__ . '/class-plugin-integration-helper.php';
181 if (file_exists($helper_file)) {
182 require_once $helper_file;
183 $predefined = \UltimatePostKit\SetupWizard\Plugin_Integration_Helper::get_predefined_plugins();
184 foreach ($predefined as $key => $config) {
185 $dir = (strpos($key, '/') !== false) ? dirname($key) : $key;
186 $recommended_by_slug[$dir] = !empty($config['recommended']);
187 }
188 }
189
190 // Format the response for frontend use
191 $formatted_plugins = [];
192 foreach ($plugins_data as $slug => $data) {
193 // Check plugin status
194 $plugin_status = self::get_plugin_status_by_slug($slug);
195 $plugin_file = self::get_plugin_file_by_slug($slug);
196
197 // Format the last updated date
198 $last_updated_formatted = '';
199 if (!empty($data['last_updated'])) {
200 $last_updated_formatted = self::format_last_updated($data['last_updated']);
201 }
202
203 $formatted_plugins[] = [
204 'name' => $data['name'] ?? '',
205 'slug' => $data['slug'] ?? '',
206 'description' => $data['description'] ?? '',
207 'logo' => $data['logo'] ?? '',
208 'rating' => $data['rating'] ?? 0,
209 'rating_percentage' => $data['rating_percentage'] ?? 0,
210 'num_ratings' => $data['num_ratings'] ?? 0,
211 'active_installs' => $data['active_installs'] ?? '0',
212 'active_installs_count' => $data['active_installs_count'] ?? 0,
213 'downloaded' => $data['downloaded'] ?? 0,
214 'downloaded_formatted' => $data['downloaded_formatted'] ?? '',
215 'version' => $data['version'] ?? '',
216 'tested' => $data['tested'] ?? '',
217 'last_updated' => $data['last_updated'] ?? '',
218 'last_updated_formatted' => $last_updated_formatted,
219 'homepage' => $data['homepage'] ?? '',
220 'status' => $plugin_status,
221 'plugin_file' => $plugin_file,
222 'activate_nonce' => $plugin_file ? wp_create_nonce('activate-plugin_' . $plugin_file) : '',
223 'recommended' => !empty($recommended_by_slug[$slug])
224 ];
225 }
226
227 wp_send_json_success([
228 'plugins' => $formatted_plugins,
229 'loading' => false,
230 'message' => __('Plugin data loaded successfully.', 'ultimate-post-kit')
231 ]);
232 }
233
234 /**
235 * Schedule the cron job on init
236 */
237 public static function schedule_cron() {
238 // Make sure the cron hook is registered
239 if (!wp_next_scheduled(self::CRON_HOOK)) {
240 // Don't schedule immediately, only when needed
241 }
242 }
243
244 /**
245 * Get plugin status by slug
246 *
247 * @param string $slug Plugin slug
248 * @return string Plugin status: 'active', 'installed', 'not_installed'
249 */
250 private static function get_plugin_status_by_slug($slug) {
251 if (!function_exists('is_plugin_active')) {
252 require_once ABSPATH . 'wp-admin/includes/plugin.php';
253 }
254
255 // Get all installed plugins
256 $installed_plugins = get_plugins();
257
258 // Find the plugin file for this slug
259 $plugin_file = self::get_plugin_file_by_slug($slug);
260
261 if ($plugin_file && is_plugin_active($plugin_file)) {
262 return 'active';
263 } elseif ($plugin_file && isset($installed_plugins[$plugin_file])) {
264 return 'installed';
265 }
266
267 return 'not_installed';
268 }
269
270 /**
271 * Get plugin file path by slug
272 *
273 * @param string $slug Plugin slug
274 * @return string|null Plugin file path or null if not found
275 */
276 private static function get_plugin_file_by_slug($slug) {
277 if (!function_exists('get_plugins')) {
278 require_once ABSPATH . 'wp-admin/includes/plugin.php';
279 }
280
281 $installed_plugins = get_plugins();
282
283 // Look for the plugin file that matches the slug
284 foreach ($installed_plugins as $plugin_file => $plugin_data) {
285 $plugin_slug = dirname($plugin_file);
286 if ($plugin_slug === $slug) {
287 return $plugin_file;
288 }
289 }
290
291 return null;
292 }
293
294 /**
295 * Format date in human-readable format
296 *
297 * @param string $date_string Date string to format
298 * @return string Formatted date string
299 */
300 private static function format_last_updated($date_string) {
301 if (empty($date_string)) {
302 return __('Unknown', 'ultimate-post-kit');
303 }
304
305 $date = strtotime($date_string);
306 if (!$date) {
307 return __('Unknown', 'ultimate-post-kit');
308 }
309
310 $diff = current_time('timestamp') - $date;
311
312 if ($diff < 60) {
313 return __('Just now', 'ultimate-post-kit');
314 } elseif ($diff < 3600) {
315 $minutes = floor($diff / 60);
316 return sprintf(_n('%d minute ago', '%d minutes ago', $minutes, 'ultimate-post-kit'), $minutes);
317 } elseif ($diff < 86400) {
318 $hours = floor($diff / 3600);
319 return sprintf(_n('%d hour ago', '%d hours ago', $hours, 'ultimate-post-kit'), $hours);
320 } elseif ($diff < 2592000) { // 30 days
321 $days = floor($diff / 86400);
322 return sprintf(_n('%d day ago', '%d days ago', $days, 'ultimate-post-kit'), $days);
323 } elseif ($diff < 31536000) { // 1 year
324 $months = floor($diff / 2592000);
325 return sprintf(_n('%d month ago', '%d months ago', $months, 'ultimate-post-kit'), $months);
326 } else {
327 $years = floor($diff / 31536000);
328 return sprintf(_n('%d year ago', '%d years ago', $years, 'ultimate-post-kit'), $years);
329 }
330 }
331
332 /**
333 * Fetch plugin data from WordPress.org API
334 *
335 * @param string $plugin_slug Plugin slug
336 * @return array|false Plugin data or false on failure
337 */
338 private static function fetch_plugin_from_api($plugin_slug) {
339 $api_url = add_query_arg([
340 'action' => 'plugin_information',
341 'request' => [
342 'slug' => $plugin_slug,
343 'fields' => [
344 'icons' => true,
345 'short_description' => true,
346 'active_installs' => true,
347 'rating' => true,
348 'num_ratings' => true,
349 'downloaded' => true,
350 'last_updated' => true,
351 'homepage' => true,
352 'tested' => true,
353 'requires' => true,
354 'requires_php' => true,
355 'sections' => false,
356 'compatibility' => false,
357 'banners' => false,
358 'contributors' => false,
359 'tags' => false,
360 'reviews' => false,
361 'versions' => false,
362 'installation' => false,
363 'faq' => false,
364 'changelog' => false,
365 'screenshots' => false,
366 'donate_link' => false,
367 ]
368 ]
369 ], 'https://api.wordpress.org/plugins/info/1.2/');
370
371 // Security: Use wp_safe_remote_get instead of wp_remote_get
372 $response = wp_safe_remote_get($api_url, [
373 'timeout' => 30,
374 'user-agent' => 'Ultimate Post Kit Setup Wizard'
375 ]);
376
377 if (is_wp_error($response)) {
378 return false;
379 }
380
381 $body = wp_remote_retrieve_body($response);
382 $data = json_decode($body, true);
383
384 if (empty($data) || !is_array($data)) {
385 return false;
386 }
387
388 $formatted_data = self::format_plugin_data($data);
389
390 if (empty($formatted_data['name']) && empty($formatted_data['slug'])) {
391 return false;
392 }
393
394 return $formatted_data;
395 }
396
397 /**
398 * Format plugin data for our use
399 *
400 * @param array $raw_data Raw API data
401 * @return array Formatted plugin data
402 */
403 private static function format_plugin_data($raw_data) {
404 // Get the best available icon with validation
405 $icon_url = self::get_valid_plugin_icon($raw_data['icons'] ?? []);
406
407 // Format active installs with null safety and real data
408 $active_installs_raw = $raw_data['active_installs'] ?? 0;
409 $active_installs = self::format_active_installs($active_installs_raw);
410 $active_installs_count = self::get_numeric_active_installs($active_installs_raw);
411
412 // Calculate rating percentage with null safety and real data
413 $rating_percentage = 0;
414 $rating_raw = $raw_data['rating'] ?? 0;
415 $num_ratings_raw = $raw_data['num_ratings'] ?? 0;
416
417 if (!empty($rating_raw) && !empty($num_ratings_raw)) {
418 $rating_percentage = ($rating_raw / 100) * 5; // Convert to 5-star scale
419 }
420
421 // Get downloaded count for additional metrics
422 $downloaded_count = $raw_data['downloaded'] ?? 0;
423
424 return [
425 'name' => $raw_data['name'] ?? '',
426 'slug' => $raw_data['slug'] ?? '',
427 'logo' => $icon_url,
428 'description' => $raw_data['short_description'] ?? '',
429 'active_installs' => $active_installs,
430 'active_installs_count' => $active_installs_count,
431 'rating' => round($rating_percentage, 1),
432 'rating_percentage' => $rating_raw,
433 'num_ratings' => $num_ratings_raw,
434 'downloaded' => $downloaded_count,
435 'downloaded_formatted' => self::format_downloaded_count($downloaded_count),
436 'last_updated' => $raw_data['last_updated'] ?? '',
437 'homepage' => $raw_data['homepage'] ?? '',
438 'version' => $raw_data['version'] ?? '',
439 'tested' => $raw_data['tested'] ?? '',
440 'requires' => $raw_data['requires'] ?? '',
441 'requires_php' => $raw_data['requires_php'] ?? '',
442 'fetched_at' => current_time('timestamp')
443 ];
444 }
445
446 /**
447 * Get valid plugin icon with format validation
448 *
449 * @param array $icons Array of icon URLs
450 * @return string Valid icon URL or empty string
451 */
452 private static function get_valid_plugin_icon($icons) {
453 $valid_extensions = ['gif', 'png', 'jpg', 'jpeg', 'svg'];
454 $icon_sizes = ['256', '128', 'default'];
455
456 foreach ($icon_sizes as $size) {
457 if (!empty($icons[$size])) {
458 $icon_url = $icons[$size];
459
460 // Check if URL is valid and has correct extension
461 if (self::is_valid_image_url($icon_url, $valid_extensions)) {
462 return $icon_url;
463 }
464 }
465 }
466
467 return '';
468 }
469
470 /**
471 * Validate image URL and extension
472 *
473 * @param string $url Image URL
474 * @param array $valid_extensions Allowed extensions
475 * @return bool True if valid
476 */
477 private static function is_valid_image_url($url, $valid_extensions) {
478 if (empty($url) || !is_string($url)) {
479 return false;
480 }
481
482 // Check if URL is valid
483 if (!filter_var($url, FILTER_VALIDATE_URL)) {
484 return false;
485 }
486
487 // Get file extension
488 $path_info = pathinfo(parse_url($url, PHP_URL_PATH));
489 $extension = strtolower($path_info['extension'] ?? '');
490
491 return in_array($extension, $valid_extensions);
492 }
493
494 /**
495 * Format active installs number with null safety
496 *
497 * @param mixed $installs Number of active installs
498 * @return string Formatted installs string
499 */
500 private static function format_active_installs($installs) {
501 // Handle null, empty, or non-numeric values
502 if (is_null($installs) || $installs === '' || !is_numeric($installs)) {
503 return '0';
504 }
505
506 $installs = intval($installs);
507
508 if ($installs >= 1000000) {
509 return round($installs / 1000000, 1) . 'M+';
510 } elseif ($installs >= 1000) {
511 return round($installs / 1000, 1) . 'K+';
512 } else {
513 return number_format($installs);
514 }
515 }
516
517 /**
518 * Get numeric active installs count
519 *
520 * @param mixed $installs Number of active installs
521 * @return int Numeric installs count
522 */
523 private static function get_numeric_active_installs($installs) {
524 // Handle null, empty, or non-numeric values
525 if (is_null($installs) || $installs === '' || !is_numeric($installs)) {
526 return 0;
527 }
528
529 return intval($installs);
530 }
531
532 /**
533 * Format downloaded count
534 *
535 * @param mixed $downloaded Number of downloads
536 * @return string Formatted downloads string
537 */
538 private static function format_downloaded_count($downloaded) {
539 // Handle null, empty, or non-numeric values
540 if (is_null($downloaded) || $downloaded === '' || !is_numeric($downloaded)) {
541 return '0';
542 }
543
544 $downloaded = intval($downloaded);
545
546 if ($downloaded >= 1000000) {
547 return round($downloaded / 1000000, 1) . 'M+';
548 } elseif ($downloaded >= 1000) {
549 return round($downloaded / 1000, 1) . 'K+';
550 } else {
551 return number_format($downloaded);
552 }
553 }
554 }
555
556 // Initialize the handler
557 add_action('init', function() {
558 Remote_Data_Handler::init();
559 });
560
561 // Global functions for backward compatibility and ease of use
562 if (!function_exists('upk_is_element_pack_page')) {
563 function upk_is_element_pack_page() {
564 return \UltimatePostKit\SetupWizard\Remote_Data_Handler::is_element_pack_page();
565 }
566 }
567
568 if (!function_exists('upk_get_remote_plugins')) {
569 function upk_get_remote_plugins() {
570 return \UltimatePostKit\SetupWizard\Remote_Data_Handler::get_remote_plugins();
571 }
572 }
573
574 if (!function_exists('upk_schedule_remote_fetch')) {
575 function upk_schedule_remote_fetch() {
576 return \UltimatePostKit\SetupWizard\Remote_Data_Handler::schedule_remote_fetch();
577 }
578 }
579
580 if (!function_exists('upk_fetch_remote_plugins_now')) {
581 function upk_fetch_remote_plugins_now() {
582 return \UltimatePostKit\SetupWizard\Remote_Data_Handler::fetch_remote_plugins_now();
583 }
584 }
585