PluginProbe
Ultimate Post Kit – Elementor Post Grid, Post Carousel, Post Slider & Blog Layout Widgets / trunk
Ultimate Post Kit – Elementor Post Grid, Post Carousel, Post Slider & Blog Layout Widgets vtrunk
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 trunk, at includes/setup-wizard/views/integration.php

533 lines 24.8 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 // Translatable strings used by the dynamically rendered plugin list.
346 const upkI18n = <?php echo wp_json_encode( array(
347 'noPlugins' => __( 'No plugins found.', 'ultimate-post-kit' ),
348 'recommended' => __( 'Recommended', 'ultimate-post-kit' ),
349 'active' => __( 'ACTIVE', 'ultimate-post-kit' ),
350 /* translators: %s: number of active installs, or the "Fewer than 10" phrase. */
351 'activeInstalls' => __( 'Active Installs: %s', 'ultimate-post-kit' ),
352 'fewerThanTen' => __( 'Fewer than 10', 'ultimate-post-kit' ),
353 /* translators: %s: formatted download count. */
354 'downloads' => __( 'Downloads: %s', 'ultimate-post-kit' ),
355 /* translators: %s: plugin rating, e.g. 4.5. */
356 'ratingTitle' => __( '%s out of 5 stars', 'ultimate-post-kit' ),
357 /* translators: %s: plugin rating, e.g. 4.5. */
358 'ratingText' => __( '%s out of 5 stars.', 'ultimate-post-kit' ),
359 /* translators: %s: number of ratings. */
360 'ratingCount' => __( '(%s ratings)', 'ultimate-post-kit' ),
361 /* translators: %s: how long ago the plugin was updated. */
362 'lastUpdated' => __( 'Last Updated: %s', 'ultimate-post-kit' ),
363 ), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT ); ?>;
364
365 // Minimal printf-style substitution so translators keep control of word order.
366 function upkFormat(template, value) {
367 return String(template).replace('%s', value);
368 }
369
370 // Function to render plugin list
371 function renderPluginList(plugins) {
372 const $pluginList = $('#upk-integration-plugin-list');
373 let html = '';
374
375 if (plugins.length === 0) {
376 html = `<div class="upk-no-plugins" style="text-align: center; padding: 40px;"><p>${upkEsc(upkI18n.noPlugins)}</p></div>`;
377 } else {
378 plugins.forEach(function(plugin) {
379 // Skip own plugin (Ultimate Post Kit) when printing only; data still includes it for other plugins
380 if (plugin.slug === 'ultimate-post-kit') return;
381 const isActive = plugin.status === 'active';
382 const isRecommended = plugin.recommended && !isActive;
383
384 html += `
385 <label class="plugin-item" data-slug="${upkEsc(plugin.slug)}">
386 <span class="bdt-flex bdt-flex-middle bdt-flex-between bdt-margin-small-bottom">
387 <span class="bdt-plugin-logo">
388 ${generatePluginLogo(plugin)}
389 </span>
390 <div class="bdt-plugin-badge-switch-wrap">
391 ${isRecommended ? `<span class="recommended-badge">${upkEsc(upkI18n.recommended)}</span>` : ''}
392 ${isActive ? `<span class="active-badge">${upkEsc(upkI18n.active)}</span>` : ''}
393 ${!isActive ? `
394 <label class="switch">
395 <input type="checkbox" class="plugin-slider-checkbox" ${plugin.recommended ? 'checked' : ''} name="plugins[]${upkEsc(plugin.slug)}">
396 <span class="slider round"></span>
397 </label>
398 ` : ''}
399 </div>
400 </span>
401 <div class="bdt-flex bdt-flex-middle">
402 <span class="bdt-plugin-name">${upkEsc(plugin.name)}</span>
403 </div>
404 <span class="active-installs">
405 ${upkFormat(upkEsc(upkI18n.activeInstalls), `<span class="installs-count">${plugin.active_installs_count > 0 ? upkEsc(plugin.active_installs_count.toLocaleString() + '+') : upkEsc(upkI18n.fewerThanTen)}</span>`)}
406 </span>
407 ${plugin.downloaded_formatted ? `<span class="downloads">${upkFormat(upkEsc(upkI18n.downloads), upkEsc(plugin.downloaded_formatted))}</span>` : ''}
408 <div class="rating-section">
409 <div class="wporg-ratings" title="${upkEsc(upkFormat(upkI18n.ratingTitle, plugin.rating))}" style="color:var(--wp--preset--color--pomegrade-1, #e26f56);">
410 ${generateStarRating(plugin.rating)}
411 </div>
412 <span class="rating-text">
413 ${upkEsc(upkFormat(upkI18n.ratingText, plugin.rating))}
414 ${plugin.num_ratings > 0 ? `<span class="rating-count">${upkEsc(upkFormat(upkI18n.ratingCount, plugin.num_ratings.toLocaleString()))}</span>` : ''}
415 </span>
416 </div>
417 ${plugin.last_updated_formatted ? `<span class="last-updated">${upkFormat(upkEsc(upkI18n.lastUpdated), upkEsc(plugin.last_updated_formatted))}</span>` : ''}
418 </label>
419 `;
420 });
421 }
422
423 $pluginList.html(html);
424 }
425
426 // Escape remote-sourced strings before they are concatenated into markup. The plugin
427 // catalog comes from a remote endpoint; treat it as untrusted so a poisoned or
428 // compromised feed cannot inject HTML/JS into the admin dashboard. Note that
429 // Remote_Data_Handler::decode_api_text() html_entity_decode()s these fields, so they
430 // arrive here already un-escaped.
431 function upkEsc(s) {
432 return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
433 return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
434 });
435 }
436
437 function upkSafeUrl(u) {
438 u = String(u == null ? '' : u);
439 return /^https?:\/\//i.test(u) ? u : '';
440 }
441
442 // Helper function to generate plugin logo
443 function generatePluginLogo(plugin) {
444 if (plugin.logo && plugin.logo.match(/^https?:\/\//)) {
445 return `<img src="${upkEsc(upkSafeUrl(plugin.logo))}" alt="${upkEsc(plugin.name)}" onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
446 <div class="default-plugin-icon" style="display:none;">📦</div>`;
447 } else {
448 // No icon supplied by the data source — show the local placeholder
449 // rather than offloading an image request to a remote host.
450 return `<div class="default-plugin-icon" style="display:flex;">📦</div>`;
451 }
452 }
453
454 // Helper function to generate star rating
455 function generateStarRating(rating) {
456 const fullStars = Math.floor(rating);
457 const hasHalfStar = (rating - fullStars) >= 0.5;
458 const emptyStars = 5 - fullStars - (hasHalfStar ? 1 : 0);
459
460 let html = '';
461 for (let i = 0; i < fullStars; i++) {
462 html += '<span class="dashicons dashicons-star-filled"></span>';
463 }
464 if (hasHalfStar) {
465 html += '<span class="dashicons dashicons-star-half"></span>';
466 }
467 for (let i = 0; i < emptyStars; i++) {
468 html += '<span class="dashicons dashicons-star-empty"></span>';
469 }
470 return html;
471 }
472
473 // Function to show error
474 function showError(message) {
475 const $pluginList = $('#upk-integration-plugin-list');
476 const $initialLoading = $('#upk-initial-loading');
477
478 // Hide initial loading
479 $initialLoading.hide();
480
481 // Show error in plugin list
482 $pluginList.html(`
483 <div class="upk-error-state" style="text-align: center; padding: 40px;">
484 <p style="color: #d63638;">${upkEsc(message)}</p>
485 <button type="button" class="bdt-button bdt-button-secondary" onclick="location.reload()">Retry</button>
486 </div>
487 `);
488 }
489
490 // Detect when integration tab becomes active
491 function observeIntegrationTab() {
492 // Check if integration step is currently visible
493 const $integrationStep = $('.bdt-setup-wizard-integration');
494
495 if ($integrationStep.length) {
496 // Create a MutationObserver to detect when the integration step becomes visible
497 const observer = new MutationObserver(function(mutations) {
498 mutations.forEach(function(mutation) {
499 if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
500 const $target = $(mutation.target);
501 if ($target.hasClass('bdt-setup-wizard-integration') && $target.is(':visible')) {
502 loadIntegrationData();
503 observer.disconnect(); // Stop observing after first load
504 }
505 }
506 });
507 });
508
509 // Start observing
510 observer.observe($integrationStep[0], {
511 attributes: true,
512 attributeFilter: ['class']
513 });
514
515 // Also check immediately if it's already visible
516 if ($integrationStep.is(':visible') && !integrationDataLoaded) {
517 loadIntegrationData();
518 }
519 }
520 }
521
522 // Initialize tab observation
523 observeIntegrationTab();
524
525 // Fallback: Also try to detect tab clicks (for different wizard implementations)
526 $(document).on('click', '[data-step="integration"], .bdt-wizard-step[data-step="integration"]', function() {
527 if (!integrationDataLoaded) {
528 setTimeout(loadIntegrationData, 100);
529 }
530 });
531 });
532 </script>
533