PluginProbe
Ultimate Post Kit – Elementor Post Grid, Post Carousel, Post Slider & Blog Layout Widgets / 4.5.2
Ultimate Post Kit – Elementor Post Grid, Post Carousel, Post Slider & Blog Layout Widgets v4.5.2
4.5.6 4.5.5 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 All 148 releases
ultimate-post-kit / includes / setup-wizard / ultimate-post-kit-others-plugin.php

ultimate-post-kit-others-plugin.php in Ultimate Post Kit – Elementor Post Grid, Post Carousel, Post Slider & Blog Layout Widgets 4.5.2, at includes/setup-wizard/ultimate-post-kit-others-plugin.php

593 lines 28.0 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 Others Plugin - Standalone Plugin Manager
4 *
5 * This file provides the enhanced plugin installation and management system
6 * for Ultimate Post Kit, separated from the main admin settings for better maintainability.
7 *
8 * @version 1.0.0
9 * @author BDThemes
10 */
11
12 if (!defined('ABSPATH')) {
13 exit;
14 }
15
16 /**
17 * Ultimate Post Kit Others Plugin Manager
18 */
19 class UltimatePostKit_Others_Plugin_Manager {
20
21 /**
22 * Constructor
23 */
24 public function __construct() {
25 // Add AJAX handlers. This is an admin-only, plugin-install screen; the
26 // handler must never be exposed to unauthenticated visitors.
27 add_action('wp_ajax_upk_get_plugins', [$this, 'ajax_get_plugins']);
28 add_action('wp_ajax_upk_install_plugin', [$this, 'install_plugin_ajax']);
29 }
30
31 /**
32 * Render the others plugin interface
33 */
34 public function render_others_plugin() {
35 // Include the required classes
36 require_once BDTUPK_INC_PATH . 'setup-wizard/class-plugin-integration-helper.php';
37 require_once BDTUPK_INC_PATH . 'setup-wizard/class-remote-data-handler.php';
38
39 // Define plugin slugs for reference
40 $plugin_slugs = array(
41 'bdthemes-element-pack-lite',
42 'bdthemes-prime-slider-lite/bdthemes-prime-slider.php',
43 'ultimate-post-kit',
44 'ultimate-store-kit',
45 'zoloblocks',
46 'pixel-gallery',
47 'live-copy-paste',
48 'spin-wheel',
49 'ai-image',
50 'dark-reader',
51 'ar-viewer',
52 'smart-admin-assistant',
53 'website-accessibility',
54 );
55
56 // Helper function for time formatting
57 if (!function_exists('format_last_updated_usk')) {
58 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- established function name relied on across the plugin family / feedback SDK; renaming would break integration.
59 function format_last_updated_usk($date_string) {
60 if (empty($date_string)) {
61 return __('Unknown', 'ultimate-post-kit');
62 }
63
64 $date = strtotime($date_string);
65 if (!$date) {
66 return __('Unknown', 'ultimate-post-kit');
67 }
68
69 $diff = current_time('timestamp') - $date;
70
71 if ($diff < 60) {
72 return __('Just now', 'ultimate-post-kit');
73 } elseif ($diff < 3600) {
74 $minutes = floor($diff / 60);
75 /* translators: %d: number of minutes */
76 return sprintf(_n('%d minute ago', '%d minutes ago', $minutes, 'ultimate-post-kit'), $minutes);
77 } elseif ($diff < 86400) {
78 $hours = floor($diff / 3600);
79 /* translators: %d: number of hours */
80 return sprintf(_n('%d hour ago', '%d hours ago', $hours, 'ultimate-post-kit'), $hours);
81 } elseif ($diff < 2592000) { // 30 days
82 $days = floor($diff / 86400);
83 /* translators: %d: number of days */
84 return sprintf(_n('%d day ago', '%d days ago', $days, 'ultimate-post-kit'), $days);
85 } elseif ($diff < 31536000) { // 1 year
86 $months = floor($diff / 2592000);
87 /* translators: %d: number of months */
88 return sprintf(_n('%d month ago', '%d months ago', $months, 'ultimate-post-kit'), $months);
89 } else {
90 $years = floor($diff / 31536000);
91 /* translators: %d: number of years */
92 return sprintf(_n('%d year ago', '%d years ago', $years, 'ultimate-post-kit'), $years);
93 }
94 }
95 }
96
97 ?>
98
99 <div class="upk-dashboard-panel"
100 bdt-scrollspy="target: > div > div > .bdt-card; cls: bdt-animation-slide-bottom-small; delay: 300">
101 <div class="upk-dashboard-others-plugin" id="upk-others-plugin-container">
102
103 <!-- Loading state -->
104 <div class="upk-plugins-loading" id="upk-plugins-loading">
105 <div class="bdt-flex bdt-flex-center bdt-flex-middle bdt-text-center" style="min-height: 200px;">
106 <div>
107 <div class="bdt-spinner bdt-spinner-primary"></div>
108 <p class="bdt-margin-small-top"><?php esc_html_e('Loading plugin data...', 'ultimate-post-kit'); ?></p>
109 </div>
110 </div>
111 </div>
112
113 <!-- Error state (hidden by default) -->
114 <div class="upk-plugins-error" id="upk-plugins-error" style="display: none;">
115 <div class="bdt-alert bdt-alert-warning" bdt-alert>
116 <a class="bdt-alert-close" bdt-close></a>
117 <p><?php esc_html_e('Unable to load plugin data. Please try again later.', 'ultimate-post-kit'); ?></p>
118 <button class="bdt-button bdt-button-small bdt-margin-small-top" id="upk-retry-load-plugins">
119 <?php esc_html_e('Retry', 'ultimate-post-kit'); ?>
120 </button>
121 </div>
122 </div>
123
124 <!-- Plugins container (populated by AJAX) -->
125 <div class="upk-plugins-list" id="upk-plugins-list" style="display: none;">
126 <!-- Plugin cards will be inserted here by JavaScript -->
127 </div>
128 </div>
129 </div>
130
131 <style type="text/css">
132 .upk-loading-spinner {
133 position: absolute;
134 top: 50%;
135 left: 50%;
136 transform: translate(-50%, -50%);
137 text-align: center;
138 }
139
140 .upk-loading-dots {
141 display: flex;
142 justify-content: center;
143 align-items: center;
144 gap: 8px;
145 margin-bottom: 15px;
146 }
147
148 .upk-loading-dot {
149 width: 12px;
150 height: 12px;
151 background-color: #0073aa;
152 border-radius: 50%;
153 animation: upk-wave 1.4s ease-in-out infinite both;
154 }
155
156 .upk-loading-dot:nth-child(1) { animation-delay: -0.32s; }
157 .upk-loading-dot:nth-child(2) { animation-delay: -0.16s; }
158 .upk-loading-dot:nth-child(3) { animation-delay: 0; }
159
160 @keyframes upk-wave {
161 0%, 80%, 100% {
162 transform: scale(0.8);
163 opacity: 0.5;
164 }
165 40% {
166 transform: scale(1.2);
167 opacity: 1;
168 }
169 }
170
171 #upk-plugins-list {
172 position: relative;
173 min-height: 200px;
174 }
175 </style>
176
177 <script type="text/javascript">
178 jQuery(document).ready(function($) {
179 var $container = $('#upk-others-plugin-container');
180 var $loading = $('#upk-plugins-loading');
181 var $error = $('#upk-plugins-error');
182 var $list = $('#upk-plugins-list');
183
184 // Function to load plugins via AJAX
185 function loadPlugins() {
186 $loading.hide();
187 $error.hide();
188 showLoading();
189
190 $.ajax({
191 url: ajaxurl,
192 type: 'POST',
193 data: {
194 action: 'upk_get_plugins',
195 nonce: '<?php echo esc_attr( wp_create_nonce("upk_get_plugins_nonce") ); ?>'
196 },
197 success: function(response) {
198 if (response.success && response.data) {
199 if (response.data.loading) {
200 // Still loading, show message and retry after delay
201 showLoading();
202 setTimeout(loadPlugins, 3000); // Retry after 3 seconds
203 } else {
204 renderPlugins(response.data.plugins);
205 }
206 } else {
207 showError();
208 }
209 },
210 error: function() {
211 showError();
212 }
213 });
214 }
215
216 // Escape remote-sourced strings before they are concatenated into
217 // markup. The plugin catalog comes from a remote endpoint; treat it
218 // as untrusted so a poisoned/compromised feed cannot inject HTML/JS
219 // into the admin dashboard (the 2026 notification-feed incident).
220 function upkEsc(s) {
221 return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
222 return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
223 });
224 }
225 function upkSafeUrl(u) {
226 u = String(u == null ? '' : u);
227 return /^https?:\/\//i.test(u) ? u : '';
228 }
229
230 // Function to render plugins
231 function renderPlugins(plugins) {
232 var html = '';
233
234 if (plugins.length === 0) {
235 html = '<div class="bdt-text-center bdt-padding-large"><p><?php echo esc_js(__('No plugins available.', 'ultimate-post-kit')); ?></p></div>';
236 } else {
237 plugins.forEach(function(plugin) {
238 // Skip own plugin (Ultimate Post Kit) when printing only; data still includes it for other plugins
239 if (plugin.slug === 'ultimate-post-kit') return;
240 var isActive = false; // We'll determine this via PHP in the actual implementation
241 var logoUrl = plugin.logo || '';
242 var pluginName = plugin.name || '';
243 var pluginSlug = plugin.slug || '';
244
245 // The logo URL comes from the WordPress.org API response. When it is
246 // missing we show the local placeholder rather than guessing a remote
247 // asset URL.
248 var logoMarkup = upkSafeUrl(logoUrl)
249 ? '<img src="' + upkEsc(upkSafeUrl(logoUrl)) + '" alt="' + upkEsc(pluginName) + '" class="bdt-plugin-logo" ' +
250 'onerror="this.style.display=\'none\'; this.nextElementSibling.style.display=\'flex\';">' +
251 '<div class="default-plugin-icon" style="display:none;">📦</div>'
252 : '<div class="default-plugin-icon" style="display:flex;">📦</div>';
253
254 html += '<div class="bdt-card bdt-card-body bdt-flex bdt-flex-middle bdt-flex-between">' +
255 '<div class="bdt-others-plugin-content">' +
256 '<div class="bdt-plugin-logo-wrap bdt-flex bdt-flex-middle">' +
257 '<div class="bdt-plugin-logo-container">' +
258 logoMarkup +
259 '</div>' +
260 '<div class="bdt-others-plugin-user-wrap bdt-flex bdt-flex-middle">' +
261 '<h1 class="upk-feature-title">' + upkEsc(pluginName) + '</h1>' +
262 '</div>' +
263 '</div>' +
264 '<div class="bdt-others-plugin-content-text bdt-margin-top">';
265
266 if (plugin.description) {
267 html += '<p>' + upkEsc(plugin.description) + '</p>';
268 }
269
270 // Active installs
271 var installsCount = Number(plugin.active_installs_count) || 0;
272 html += '<span class="active-installs bdt-margin-small-top">' +
273 '<?php echo esc_js(__('Active Installs: ', 'ultimate-post-kit')); ?> ';
274 if (installsCount > 0) {
275 html += '<span class="installs-count">' + upkEsc(installsCount.toLocaleString()) + '+</span>';
276 } else {
277 html += '<span class="installs-count"><?php echo esc_js(__('Fewer than 10', 'ultimate-post-kit')); ?></span>';
278 }
279 html += '</span>';
280
281 // Rating
282 html += '<div class="bdt-others-plugin-rating bdt-margin-small-top bdt-flex bdt-flex-middle">' +
283 '<span class="bdt-others-plugin-rating-stars">';
284
285 // Clamp to 0-5 so malformed data cannot emit a runaway number of stars.
286 var rating = Math.min(5, Math.max(0, parseFloat(plugin.rating) || 0));
287 var fullStars = Math.floor(rating);
288 var hasHalfStar = (rating - fullStars) >= 0.5;
289 var emptyStars = 5 - fullStars - (hasHalfStar ? 1 : 0);
290 var i;
291
292 for (i = 0; i < fullStars; i++) {
293 html += '<i class="dashicons dashicons-star-filled"></i>';
294 }
295 if (hasHalfStar) {
296 html += '<i class="dashicons dashicons-star-half"></i>';
297 }
298 for (i = 0; i < emptyStars; i++) {
299 html += '<i class="dashicons dashicons-star-empty"></i>';
300 }
301
302 html += '</span>' +
303 '<span class="bdt-others-plugin-rating-text bdt-margin-small-left">' +
304 rating + ' <?php echo esc_js(__('out of 5 stars.', 'ultimate-post-kit')); ?>';
305
306 var numRatings = Number(plugin.num_ratings) || 0;
307 if (numRatings > 0) {
308 html += '<span class="rating-count">(' + upkEsc(numRatings.toLocaleString()) + ' <?php echo esc_js(__('ratings', 'ultimate-post-kit')); ?>)</span>';
309 }
310
311 html += '</span></div>';
312
313 // Downloads
314 if (plugin.downloaded_formatted) {
315 html += '<div class="bdt-others-plugin-downloads bdt-margin-small-top">' +
316 '<span><?php echo esc_js(__('Downloads: ', 'ultimate-post-kit')); ?>' + upkEsc(plugin.downloaded_formatted) + '</span>' +
317 '</div>';
318 }
319
320 // Last updated
321 if (plugin.last_updated_formatted) {
322 html += '<div class="bdt-others-plugin-updated bdt-margin-small-top">' +
323 '<span><?php echo esc_js(__('Last Updated: ', 'ultimate-post-kit')); ?>' + upkEsc(plugin.last_updated_formatted) + '</span>' +
324 '</div>';
325 }
326
327 html += '</div></div>' +
328 '<div class="bdt-others-plugins-link">';
329
330 // Show different buttons based on plugin status
331 if (plugin.status === 'active') {
332 html += '<span class="bdt-button bdt-button-success bdt-disabled">' +
333 '<span class="dashicons dashicons-yes"></span> ' +
334 '<?php echo esc_js(__('Active', 'ultimate-post-kit')); ?>' +
335 '</span>';
336 } else if (plugin.status === 'installed') {
337 // URL-encode the query values: plugin_file contains slashes and
338 // both parts land inside an href attribute.
339 var activateUrl = <?php echo wp_json_encode( esc_url_raw( admin_url( 'plugins.php?action=activate&plugin=' ) ) ); ?> +
340 encodeURIComponent(plugin.plugin_file || '') +
341 '&_wpnonce=' + encodeURIComponent(plugin.activate_nonce || '');
342 html += '<a class="bdt-button bdt-welcome-button" href="' + upkEsc(activateUrl) + '">' +
343 '<?php echo esc_js(__('Activate', 'ultimate-post-kit')); ?>' +
344 '</a>';
345 } else {
346 html += '<button type="button" class="bdt-button bdt-welcome-button upk-install-plugin" data-plugin-slug="' + upkEsc(pluginSlug) + '" data-nonce="<?php echo esc_attr( wp_create_nonce('upk_install_plugin_nonce') ); ?>">' +
347 '<?php echo esc_js(__('Install', 'ultimate-post-kit')); ?>' +
348 '</button>';
349 }
350
351 if (plugin.homepage && upkSafeUrl(plugin.homepage)) {
352 html += '<a class="bdt-button bdt-dashboard-sec-btn" target="_blank" rel="noopener noreferrer" href="' + upkEsc(upkSafeUrl(plugin.homepage)) + '">' +
353 '<?php echo esc_js(__('Learn More', 'ultimate-post-kit')); ?>' +
354 '</a>';
355 }
356
357 html += '</div></div>';
358 });
359 }
360
361 $list.html(html);
362
363 // Handle plugin action buttons. Delegated from the list and
364 // namespaced+unbound first: renderPlugins() runs again on every
365 // retry, and a plain global bind stacked one handler per render,
366 // firing duplicate install requests for a single click.
367 $list.off('click.upkInstall').on('click.upkInstall', '.upk-install-plugin', function(e) {
368 e.preventDefault();
369
370 var $button = $(this);
371 var pluginSlug = $button.data('plugin-slug');
372 var nonce = $button.data('nonce');
373 var originalText = $button.text();
374
375 // Disable button and show loading state
376 $button.prop('disabled', true)
377 .text('<?php echo esc_js(__('Installing...', 'ultimate-post-kit')); ?>')
378 .addClass('bdt-installing');
379
380 // Perform AJAX request
381 $.ajax({
382 url: '<?php echo esc_url( admin_url('admin-ajax.php') ); ?>',
383 type: 'POST',
384 data: {
385 action: 'upk_install_plugin',
386 plugin_slug: pluginSlug,
387 nonce: nonce
388 },
389 success: function(response) {
390 if (response.success) {
391 // Show success message
392 $button.text('<?php echo esc_js(__('Installed!', 'ultimate-post-kit')); ?>')
393 .removeClass('bdt-installing')
394 .addClass('bdt-installed');
395
396 // Show success notification
397 if (typeof bdtUIkit !== 'undefined' && bdtUIkit.notification) {
398 bdtUIkit.notification({
399 message: '<span class="dashicons dashicons-yes"></span> ' + response.data.message,
400 status: 'success'
401 });
402 }
403
404 // Reload the page after 2 seconds to update button states
405 setTimeout(function() {
406 window.location.reload();
407 }, 2000);
408
409 } else {
410 // Show error message
411 $button.prop('disabled', false)
412 .text(originalText)
413 .removeClass('bdt-installing');
414
415 // Show error notification
416 if (typeof bdtUIkit !== 'undefined' && bdtUIkit.notification) {
417 bdtUIkit.notification({
418 message: '<span class="dashicons dashicons-warning"></span> ' + response.data.message,
419 status: 'danger'
420 });
421 } else {
422 alert('Error: ' + response.data.message);
423 }
424 }
425 },
426 error: function(xhr, status, error) {
427 // Show error message
428 $button.prop('disabled', false)
429 .text(originalText)
430 .removeClass('bdt-installing');
431
432 // Show error notification
433 if (typeof bdtUIkit !== 'undefined' && bdtUIkit.notification) {
434 bdtUIkit.notification({
435 message: '<span class="dashicons dashicons-warning"></span> <?php echo esc_js(__('Installation failed. Please try again.', 'ultimate-post-kit')); ?>',
436 status: 'danger'
437 });
438 } else {
439 alert('<?php echo esc_js(__('Installation failed. Please try again.', 'ultimate-post-kit')); ?>');
440 }
441 }
442 });
443 });
444 }
445
446 // Function to show loading state
447 function showLoading() {
448 $list.html(
449 '<div class="bdt-text-center bdt-padding-large">' +
450 '<div class="upk-loading-spinner">' +
451 '<div class="upk-loading-dots">' +
452 '<div class="upk-loading-dot"></div>' +
453 '<div class="upk-loading-dot"></div>' +
454 '<div class="upk-loading-dot"></div>' +
455 '</div>' +
456 '</div>' +
457 '<p class="bdt-margin-small-top bdt-text-muted"><?php echo esc_js(__('Loading plugin data...', 'ultimate-post-kit')); ?></p>' +
458 '</div>'
459 );
460 $list.show();
461 }
462
463 // Function to show error
464 function showError() {
465 $error.show();
466 $list.hide();
467 }
468
469 // Retry button handler
470 $('#upk-retry-load-plugins').on('click', function() {
471 loadPlugins();
472 });
473
474 // Initial load
475 loadPlugins();
476 });
477 </script>
478 <?php
479 }
480
481 /**
482 * AJAX handler for getting plugins data
483 */
484 public function ajax_get_plugins() {
485 // Verify nonce. Respond with JSON -- the caller parses the response as
486 // JSON, so wp_die() here would surface as a generic "unable to load".
487 if (!check_ajax_referer('upk_get_plugins_nonce', 'nonce', false)) {
488 wp_send_json_error(['message' => __('Security check failed.', 'ultimate-post-kit')], 403);
489 }
490
491 // This data is only ever used on the plugin-install screen; gate it to
492 // users who could act on it rather than exposing it to any visitor.
493 if (!current_user_can('install_plugins')) {
494 wp_send_json_error(['message' => __('You do not have permission to do this.', 'ultimate-post-kit')], 403);
495 }
496
497 // Get cached data
498 $plugins_data = \UltimatePostKit\SetupWizard\Remote_Data_Handler::get_remote_plugins();
499
500 // If cache is empty, try to fetch immediately (but don't block)
501 if (empty($plugins_data)) {
502 // Schedule background fetch if not already done
503 \UltimatePostKit\SetupWizard\Remote_Data_Handler::schedule_remote_fetch();
504
505 // Return empty response with flag indicating data is loading
506 wp_send_json_success([
507 'plugins' => [],
508 'loading' => true,
509 'message' => __('Loading plugin data...', 'ultimate-post-kit')
510 ]);
511 }
512
513 // Send response
514 wp_send_json_success([
515 'plugins' => $plugins_data,
516 'loading' => false,
517 'message' => __('Plugin data loaded successfully.', 'ultimate-post-kit')
518 ]);
519 }
520
521 /**
522 * AJAX handler for plugin installation
523 */
524 public function install_plugin_ajax() {
525 // Check nonce
526 $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
527 if (!wp_verify_nonce( $nonce, 'upk_install_plugin_nonce')) {
528 wp_send_json_error(['message' => __('Security check failed', 'ultimate-post-kit')]);
529 }
530
531 // Check user capability
532 if (!current_user_can('install_plugins')) {
533 wp_send_json_error(['message' => __('You do not have permission to install plugins', 'ultimate-post-kit')]);
534 }
535
536 $plugin_slug = isset($_POST['plugin_slug']) ? sanitize_text_field(wp_unslash($_POST['plugin_slug'])) : '';
537
538 if (empty($plugin_slug)) {
539 wp_send_json_error(['message' => __('Plugin slug is required', 'ultimate-post-kit')]);
540 }
541
542 // Include necessary WordPress files
543 require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
544 require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
545 require_once ABSPATH . 'wp-admin/includes/class-wp-ajax-upgrader-skin.php';
546
547 // Get plugin information
548 $api = plugins_api('plugin_information', [
549 'slug' => $plugin_slug,
550 'fields' => [
551 'sections' => false,
552 ],
553 ]);
554
555 if (is_wp_error($api)) {
556 wp_send_json_error(['message' => __('Plugin not found: ', 'ultimate-post-kit') . $api->get_error_message()]);
557 }
558
559 // Install the plugin
560 $skin = new \WP_Ajax_Upgrader_Skin();
561 $upgrader = new \Plugin_Upgrader($skin);
562 $result = $upgrader->install($api->download_link);
563
564 if (is_wp_error($result)) {
565 wp_send_json_error(['message' => __('Installation failed: ', 'ultimate-post-kit') . $result->get_error_message()]);
566 } elseif ($skin->get_errors()->has_errors()) {
567 wp_send_json_error(['message' => __('Installation failed: ', 'ultimate-post-kit') . $skin->get_error_messages()]);
568 } elseif (is_null($result)) {
569 wp_send_json_error(['message' => __('Installation failed: Unable to connect to filesystem', 'ultimate-post-kit')]);
570 }
571
572 // Get installation status
573 $install_status = install_plugin_install_status($api);
574
575 wp_send_json_success([
576 'message' => __('Plugin installed successfully!', 'ultimate-post-kit'),
577 'plugin_file' => $install_status['file'],
578 'plugin_name' => $api->name
579 ]);
580 }
581 }
582
583 // Initialize the manager
584 new UltimatePostKit_Others_Plugin_Manager();
585
586 /**
587 * Helper function for easy rendering
588 */
589 function ultimate_post_kit_others_plugin() {
590 $manager = new UltimatePostKit_Others_Plugin_Manager();
591 $manager->render_others_plugin();
592 }
593