PluginProbe
Ultimate Post Kit – Elementor Post Grid, Post Carousel, Post Slider & Blog Layout Widgets / 4.5.3
Ultimate Post Kit – Elementor Post Grid, Post Carousel, Post Slider & Blog Layout Widgets v4.5.3
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 / views / integration.php

integration.php in Ultimate Post Kit – Elementor Post Grid, Post Carousel, Post Slider & Blog Layout Widgets 4.5.3, at includes/setup-wizard/views/integration.php

509 lines 23.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Integration Step
4 */
5
6 namespace UltimatePostKit\SetupWizard;
7
8 if (!defined('ABSPATH')) {
9 exit;
10 }
11
12 // phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- template partial included within a method; variables are method-scoped, not global.
13
14 // Include the required classes
15 require_once __DIR__ . '/../class-plugin-integration-helper.php';
16 require_once __DIR__ . '/../class-remote-data-handler.php';
17
18 // Helper function for time formatting
19 if (!function_exists('format_last_updated_usk')) {
20 function format_last_updated_usk($date_string) {
21 if (empty($date_string)) {
22 return __('Unknown', 'ultimate-post-kit');
23 }
24
25 $date = strtotime($date_string);
26 if (!$date) {
27 return __('Unknown', 'ultimate-post-kit');
28 }
29
30 $diff = current_time('timestamp') - $date;
31
32 if ($diff < 60) {
33 return __('Just now', 'ultimate-post-kit');
34 } elseif ($diff < 3600) {
35 $minutes = floor($diff / 60);
36 /* translators: %d: number of minutes */
37 return sprintf(_n('%d minute ago', '%d minutes ago', $minutes, 'ultimate-post-kit'), $minutes);
38 } elseif ($diff < 86400) {
39 $hours = floor($diff / 3600);
40 /* translators: %d: number of hours */
41 return sprintf(_n('%d hour ago', '%d hours ago', $hours, 'ultimate-post-kit'), $hours);
42 } elseif ($diff < 2592000) { // 30 days
43 $days = floor($diff / 86400);
44 /* translators: %d: number of days */
45 return sprintf(_n('%d day ago', '%d days ago', $days, 'ultimate-post-kit'), $days);
46 } elseif ($diff < 31536000) { // 1 year
47 $months = floor($diff / 2592000);
48 /* translators: %d: number of months */
49 return sprintf(_n('%d month ago', '%d months ago', $months, 'ultimate-post-kit'), $months);
50 } else {
51 $years = floor($diff / 31536000);
52 /* translators: %d: number of years */
53 return sprintf(_n('%d year ago', '%d years ago', $years, 'ultimate-post-kit'), $years);
54 }
55 }
56 }
57
58 // Helper function for fallback URLs
59
60 // Define plugin slugs
61 $plugin_slugs = array(
62 'bdthemes-element-pack-lite',
63 'bdthemes-prime-slider-lite/bdthemes-prime-slider.php',
64 'ultimate-post-kit',
65 'ultimate-store-kit',
66 'zoloblocks',
67 'pixel-gallery',
68 'live-copy-paste',
69 'spin-wheel',
70 'ai-image',
71 'dark-reader',
72 'ar-viewer',
73 'smart-admin-assistant',
74 'website-accessibility',
75 );
76
77 // Get enhanced plugin data using the remote data handler
78 $upk_plugins = Remote_Data_Handler::get_remote_plugins();
79
80 // Check if we have cached data
81 $has_cached_data = !empty($upk_plugins);
82
83 // If no cached data, don't fetch immediately - let JavaScript handle it
84 if (!$has_cached_data) {
85 $upk_plugins = []; // Empty array for initial load
86 }
87 ?>
88
89 <div class="bdt-wizard-step bdt-setup-wizard-integration" data-step="integration">
90 <h2><?php esc_html_e('Add More Firepower', 'ultimate-post-kit'); ?></h2>
91 <p><?php esc_html_e('You can onboard additional powerful plugins to extend your web design capabilities.', 'ultimate-post-kit'); ?></p>
92
93 <div class="progress-bar-container">
94 <div id="plugin-install-progress" class="progress-bar"></div>
95 </div>
96
97 <form method="POST" id="upk-install-plugins">
98 <!-- Loading state - shown during plugin installation -->
99 <div class="upk-loading-state" id="upk-install-loading" style="display: none; text-align: center; padding: 40px;">
100 <div class="upk-loading-dots">
101 <div class="upk-loading-dot"></div>
102 <div class="upk-loading-dot"></div>
103 <div class="upk-loading-dot"></div>
104 </div>
105 <p style="margin-top: 20px;" id="upk-loading-message"><?php esc_html_e('Installing plugins...', 'ultimate-post-kit'); ?></p>
106 </div>
107
108 <!-- Initial loading state - shown while fetching plugin data -->
109 <?php if (!$has_cached_data): ?>
110 <div class="upk-loading-state" id="upk-initial-loading" style="text-align: center; padding: 40px;">
111 <div class="upk-loading-dots">
112 <div class="upk-loading-dot"></div>
113 <div class="upk-loading-dot"></div>
114 <div class="upk-loading-dot"></div>
115 </div>
116 <p style="margin-top: 20px;"><?php esc_html_e('Loading plugin data...', 'ultimate-post-kit'); ?></p>
117 </div>
118 <?php endif; ?>
119
120 <div class="bdt-plugin-list" id="upk-integration-plugin-list">
121 <?php if ($has_cached_data): ?>
122 <?php
123 $predefined = \UltimatePostKit\SetupWizard\Plugin_Integration_Helper::get_predefined_plugins();
124 $recommended_by_slug = [];
125 foreach ($predefined as $key => $config) {
126 $dir = (strpos($key, '/') !== false) ? dirname($key) : $key;
127 $recommended_by_slug[ $dir ] = !empty($config['recommended']);
128 }
129 foreach ($upk_plugins as $slug_key => $plugin) :
130 // Skip own plugin (Ultimate Post Kit) when printing only; data still includes it for other plugins
131 if ($slug_key === 'ultimate-post-kit' ) continue;
132 // Use enhanced status if available, otherwise fall back to old method
133 $plugin_status = $plugin['status'] ?? 'unknown';
134 if ($plugin_status === 'unknown') {
135 // Fallback to old method for compatibility
136 $is_active = is_plugin_active($plugin['slug']);
137 } else {
138 // Use enhanced status
139 $is_active = ($plugin_status === 'active');
140 }
141 $plugin_recommended = !empty($recommended_by_slug[ $slug_key ]);
142 $is_recommended = $plugin_recommended && !$is_active;
143 ?>
144 <label class="plugin-item" data-slug="<?php echo esc_attr($plugin['slug']); ?>">
145 <span class="bdt-flex bdt-flex-middle bdt-flex-between bdt-margin-small-bottom">
146 <span class="bdt-plugin-logo">
147 <?php
148 $logo_url = $plugin['logo'] ?? '';
149 $plugin_name = $plugin['name'] ?? '';
150 $plugin_slug = $plugin['slug'] ?? '';
151
152 if (!empty($logo_url) && filter_var($logo_url, FILTER_VALIDATE_URL)) {
153 // Show the original logo from API
154 echo '<img src="' . esc_url($logo_url) . '" alt="' . esc_attr($plugin_name) . '" onerror="this.style.display=\'none\'; this.nextElementSibling.style.display=\'flex\';">';
155 echo '<div class="default-plugin-icon" style="display:none;">📦</div>';
156 } else {
157 // Generate fallback URLs for WordPress.org
158 // No icon in the API response, show the local placeholder.
159 echo '<div class="default-plugin-icon" style="display:flex;">📦</div>';
160 }
161 ?>
162 </span>
163
164 <div class="bdt-plugin-badge-switch-wrap">
165
166 <?php if ($is_recommended) : ?>
167 <span class="recommended-badge"><?php esc_html_e('Recommended', 'ultimate-post-kit'); ?></span>
168 <?php endif; ?>
169
170 <?php if ($is_active) : ?>
171 <span class="active-badge"><?php esc_html_e('ACTIVE', 'ultimate-post-kit'); ?></span>
172 <?php endif; ?>
173 <?php
174 if (!$is_active) : ?>
175 <label class="switch">
176 <input type="checkbox" class="plugin-slider-checkbox" <?php echo $plugin_recommended ? 'checked' : ''; ?>
177 name="plugins[]<?php echo isset($plugin['slug']) ? esc_attr($plugin['slug']) : ''; ?>">
178 <span class="slider round"></span>
179 </label>
180 <?php
181 endif;
182 ?>
183 </div>
184 </span>
185 <div class="bdt-flex bdt-flex-middle">
186 <span class="bdt-plugin-name">
187 <?php echo esc_html($plugin['name']); ?>
188 </span>
189 </div>
190
191 <span class="active-installs">
192 <?php esc_html_e('Active Installs: ', 'ultimate-post-kit');
193 if (isset($plugin['active_installs_count']) && $plugin['active_installs_count'] > 0) {
194 echo ' <span class="installs-count">' . number_format($plugin['active_installs_count']) . '+' . '</span>';
195 } else {
196 echo '<span class="installs-count">Fewer than 10</span>';
197 }
198 ?>
199 </span>
200
201 <?php if (isset($plugin['downloaded_formatted']) && !empty($plugin['downloaded_formatted'])): ?>
202 <span class="downloads"><?php esc_html_e('Downloads: ', 'ultimate-post-kit'); echo esc_html($plugin['downloaded_formatted']); ?></span>
203 <?php endif; ?>
204
205 <div class="rating-section">
206 <div class="wporg-ratings" title="<?php echo esc_attr($plugin['rating'] ?? '0'); ?> out of 5 stars" style="color:var(--wp--preset--color--pomegrade-1, #e26f56);">
207 <?php
208 $rating = floatval($plugin['rating'] ?? 0);
209 $full_stars = floor($rating);
210 $has_half_star = ($rating - $full_stars) >= 0.5;
211 $empty_stars = 5 - $full_stars - ($has_half_star ? 1 : 0);
212
213 // Full stars
214 for ($i = 0; $i < $full_stars; $i++) {
215 echo '<span class="dashicons dashicons-star-filled"></span>';
216 }
217
218 // Half star
219 if ($has_half_star) {
220 echo '<span class="dashicons dashicons-star-half"></span>';
221 }
222
223 // Empty stars
224 for ($i = 0; $i < $empty_stars; $i++) {
225 echo '<span class="dashicons dashicons-star-empty"></span>';
226 }
227 ?>
228 </div>
229 <span class="rating-text">
230 <?php echo esc_html($plugin['rating'] ?? '0'); ?> out of 5 stars.
231 <?php if (isset($plugin['num_ratings']) && $plugin['num_ratings'] > 0): ?>
232 <span class="rating-count">(<?php echo number_format($plugin['num_ratings']); ?> ratings)</span>
233 <?php endif; ?>
234 </span>
235 </div>
236
237 <?php
238 // Use the enhanced last_updated_formatted if available, otherwise fall back to formatting
239 if (isset($plugin['last_updated_formatted']) && !empty($plugin['last_updated_formatted'])): ?>
240 <span class="last-updated"><?php esc_html_e('Last Updated: ', 'ultimate-post-kit'); echo esc_html($plugin['last_updated_formatted']); ?></span>
241 <?php elseif (isset($plugin['last_updated']) && !empty($plugin['last_updated'])): ?>
242 <span class="last-updated"><?php esc_html_e('Last Updated: ', 'ultimate-post-kit'); echo esc_html(format_last_updated_usk($plugin['last_updated'])); ?></span>
243 <?php endif; ?>
244
245 </label>
246 <?php
247 endforeach; ?>
248 <?php endif; ?>
249 </div>
250
251 <div class="wizard-navigation bdt-margin-top">
252 <button class="bdt-button bdt-button-primary d-none" type="submit" id="upk-install-plugins-btn">
253 <?php esc_html_e('Install and Continue', 'ultimate-post-kit'); ?>
254 </button>
255 <div class="bdt-close-button bdt-margin-left bdt-wizard-next" data-step="finish"><?php esc_html_e('Skip', 'ultimate-post-kit'); ?></div>
256 </div>
257 </form>
258
259 <div class="bdt-wizard-navigation">
260 <button class="bdt-button bdt-button-secondary bdt-wizard-prev" data-step="features">
261 <span><i class="dashicons dashicons-arrow-left-alt"></i></span>
262 <?php esc_html_e('Previous Step', 'ultimate-post-kit'); ?>
263 </button>
264 </div>
265 </div>
266
267 <style>
268 .upk-loading-dots {
269 display: flex;
270 justify-content: center;
271 gap: 8px;
272 margin: 20px 0;
273 }
274
275 .upk-loading-dot {
276 width: 12px;
277 height: 12px;
278 background-color: #0073aa;
279 border-radius: 50%;
280 animation: upk-wave 1.4s ease-in-out infinite both;
281 }
282
283 .upk-loading-dot:nth-child(1) {
284 animation-delay: -0.32s;
285 }
286
287 .upk-loading-dot:nth-child(2) {
288 animation-delay: -0.16s;
289 }
290
291 .upk-loading-dot:nth-child(3) {
292 animation-delay: 0s;
293 }
294
295 @keyframes upk-wave {
296 0%, 80%, 100% {
297 transform: scale(0.8);
298 opacity: 0.5;
299 }
300 40% {
301 transform: scale(1.2);
302 opacity: 1;
303 }
304 }
305 </style>
306
307 <script>
308 jQuery(document).ready(function($) {
309 let integrationDataLoaded = false;
310
311 // Function to load integration data
312 function loadIntegrationData() {
313 if (integrationDataLoaded) return;
314
315 const $pluginList = $('#upk-integration-plugin-list');
316 const $initialLoading = $('#upk-initial-loading');
317
318 // Don't add another loading state if initial loading is visible
319 // Just keep the existing one
320
321 // Make AJAX request to get plugin data
322 $.ajax({
323 url: ajaxurl,
324 type: 'POST',
325 data: {
326 action: 'upk_get_plugins',
327 nonce: '<?php echo esc_attr( wp_create_nonce('upk_get_plugins_nonce') ); ?>'
328 },
329 success: function(response) {
330 if (response.success && response.data.plugins) {
331 // Hide the initial loading div
332 $initialLoading.hide();
333 renderPluginList(response.data.plugins);
334 integrationDataLoaded = true;
335 } else {
336 showError('Unable to load plugin data.');
337 }
338 },
339 error: function() {
340 showError('Network error occurred while loading plugin data.');
341 }
342 });
343 }
344
345 // Function to render plugin list
346 function renderPluginList(plugins) {
347 const $pluginList = $('#upk-integration-plugin-list');
348 let html = '';
349
350 if (plugins.length === 0) {
351 html = '<div class="upk-no-plugins" style="text-align: center; padding: 40px;"><p>No plugins found.</p></div>';
352 } else {
353 plugins.forEach(function(plugin) {
354 // Skip own plugin (Ultimate Post Kit) when printing only; data still includes it for other plugins
355 if (plugin.slug === 'ultimate-post-kit') return;
356 const isActive = plugin.status === 'active';
357 const isRecommended = plugin.recommended && !isActive;
358
359 html += `
360 <label class="plugin-item" data-slug="${upkEsc(plugin.slug)}">
361 <span class="bdt-flex bdt-flex-middle bdt-flex-between bdt-margin-small-bottom">
362 <span class="bdt-plugin-logo">
363 ${generatePluginLogo(plugin)}
364 </span>
365 <div class="bdt-plugin-badge-switch-wrap">
366 ${isRecommended ? '<span class="recommended-badge">Recommended</span>' : ''}
367 ${isActive ? '<span class="active-badge">ACTIVE</span>' : ''}
368 ${!isActive ? `
369 <label class="switch">
370 <input type="checkbox" class="plugin-slider-checkbox" ${plugin.recommended ? 'checked' : ''} name="plugins[]${upkEsc(plugin.slug)}">
371 <span class="slider round"></span>
372 </label>
373 ` : ''}
374 </div>
375 </span>
376 <div class="bdt-flex bdt-flex-middle">
377 <span class="bdt-plugin-name">${upkEsc(plugin.name)}</span>
378 </div>
379 <span class="active-installs">
380 Active Installs:
381 <span class="installs-count">${plugin.active_installs_count > 0 ? plugin.active_installs_count.toLocaleString() + '+' : 'Fewer than 10'}</span>
382 </span>
383 ${plugin.downloaded_formatted ? `<span class="downloads">Downloads: ${upkEsc(plugin.downloaded_formatted)}</span>` : ''}
384 <div class="rating-section">
385 <div class="wporg-ratings" title="${upkEsc(plugin.rating)} out of 5 stars" style="color:var(--wp--preset--color--pomegrade-1, #e26f56);">
386 ${generateStarRating(plugin.rating)}
387 </div>
388 <span class="rating-text">
389 ${upkEsc(plugin.rating)} out of 5 stars.
390 ${plugin.num_ratings > 0 ? `<span class="rating-count">(${plugin.num_ratings.toLocaleString()} ratings)</span>` : ''}
391 </span>
392 </div>
393 ${plugin.last_updated_formatted ? `<span class="last-updated">Last Updated: ${upkEsc(plugin.last_updated_formatted)}</span>` : ''}
394 </label>
395 `;
396 });
397 }
398
399 $pluginList.html(html);
400 }
401
402 // Escape remote-sourced strings before they are concatenated into markup. The plugin
403 // catalog comes from a remote endpoint; treat it as untrusted so a poisoned or
404 // compromised feed cannot inject HTML/JS into the admin dashboard. Note that
405 // Remote_Data_Handler::decode_api_text() html_entity_decode()s these fields, so they
406 // arrive here already un-escaped.
407 function upkEsc(s) {
408 return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
409 return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
410 });
411 }
412
413 function upkSafeUrl(u) {
414 u = String(u == null ? '' : u);
415 return /^https?:\/\//i.test(u) ? u : '';
416 }
417
418 // Helper function to generate plugin logo
419 function generatePluginLogo(plugin) {
420 if (plugin.logo && plugin.logo.match(/^https?:\/\//)) {
421 return `<img src="${upkEsc(upkSafeUrl(plugin.logo))}" alt="${upkEsc(plugin.name)}" onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
422 <div class="default-plugin-icon" style="display:none;">📦</div>`;
423 } else {
424 // No icon supplied by the data source — show the local placeholder
425 // rather than offloading an image request to a remote host.
426 return `<div class="default-plugin-icon" style="display:flex;">📦</div>`;
427 }
428 }
429
430 // Helper function to generate star rating
431 function generateStarRating(rating) {
432 const fullStars = Math.floor(rating);
433 const hasHalfStar = (rating - fullStars) >= 0.5;
434 const emptyStars = 5 - fullStars - (hasHalfStar ? 1 : 0);
435
436 let html = '';
437 for (let i = 0; i < fullStars; i++) {
438 html += '<span class="dashicons dashicons-star-filled"></span>';
439 }
440 if (hasHalfStar) {
441 html += '<span class="dashicons dashicons-star-half"></span>';
442 }
443 for (let i = 0; i < emptyStars; i++) {
444 html += '<span class="dashicons dashicons-star-empty"></span>';
445 }
446 return html;
447 }
448
449 // Function to show error
450 function showError(message) {
451 const $pluginList = $('#upk-integration-plugin-list');
452 const $initialLoading = $('#upk-initial-loading');
453
454 // Hide initial loading
455 $initialLoading.hide();
456
457 // Show error in plugin list
458 $pluginList.html(`
459 <div class="upk-error-state" style="text-align: center; padding: 40px;">
460 <p style="color: #d63638;">${upkEsc(message)}</p>
461 <button type="button" class="bdt-button bdt-button-secondary" onclick="location.reload()">Retry</button>
462 </div>
463 `);
464 }
465
466 // Detect when integration tab becomes active
467 function observeIntegrationTab() {
468 // Check if integration step is currently visible
469 const $integrationStep = $('.bdt-setup-wizard-integration');
470
471 if ($integrationStep.length) {
472 // Create a MutationObserver to detect when the integration step becomes visible
473 const observer = new MutationObserver(function(mutations) {
474 mutations.forEach(function(mutation) {
475 if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
476 const $target = $(mutation.target);
477 if ($target.hasClass('bdt-setup-wizard-integration') && $target.is(':visible')) {
478 loadIntegrationData();
479 observer.disconnect(); // Stop observing after first load
480 }
481 }
482 });
483 });
484
485 // Start observing
486 observer.observe($integrationStep[0], {
487 attributes: true,
488 attributeFilter: ['class']
489 });
490
491 // Also check immediately if it's already visible
492 if ($integrationStep.is(':visible') && !integrationDataLoaded) {
493 loadIntegrationData();
494 }
495 }
496 }
497
498 // Initialize tab observation
499 observeIntegrationTab();
500
501 // Fallback: Also try to detect tab clicks (for different wizard implementations)
502 $(document).on('click', '[data-step="integration"], .bdt-wizard-step[data-step="integration"]', function() {
503 if (!integrationDataLoaded) {
504 setTimeout(loadIntegrationData, 100);
505 }
506 });
507 });
508 </script>
509