PluginProbe
Ultimate Post Kit – Elementor Post Grid, Post Carousel, Post Slider & Blog Layout Widgets / 4.5.6
Ultimate Post Kit – Elementor Post Grid, Post Carousel, Post Slider & Blog Layout Widgets v4.5.6
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.6, at includes/setup-wizard/ultimate-post-kit-others-plugin.php

592 lines 28.1 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 // Active installs
267 var installsCount = Number(plugin.active_installs_count) || 0;
268 html += '<span class="active-installs bdt-margin-small-top">' +
269 '<?php echo esc_js(__('Active Installs: ', 'ultimate-post-kit')); ?> ';
270 if (installsCount > 0) {
271 html += '<span class="installs-count">' + upkEsc(installsCount.toLocaleString()) + '+</span>';
272 } else {
273 html += '<span class="installs-count"><?php echo esc_js(__('Fewer than 10', 'ultimate-post-kit')); ?></span>';
274 }
275 html += '</span>';
276
277 // Rating
278 html += '<div class="bdt-others-plugin-rating bdt-margin-small-top bdt-flex bdt-flex-middle">' +
279 '<span class="bdt-others-plugin-rating-stars">';
280
281 // Clamp to 0-5 so malformed data cannot emit a runaway number of stars.
282 var rating = Math.min(5, Math.max(0, parseFloat(plugin.rating) || 0));
283 var fullStars = Math.floor(rating);
284 var hasHalfStar = (rating - fullStars) >= 0.5;
285 var emptyStars = 5 - fullStars - (hasHalfStar ? 1 : 0);
286 var i;
287
288 for (i = 0; i < fullStars; i++) {
289 html += '<i class="dashicons dashicons-star-filled"></i>';
290 }
291 if (hasHalfStar) {
292 html += '<i class="dashicons dashicons-star-half"></i>';
293 }
294 for (i = 0; i < emptyStars; i++) {
295 html += '<i class="dashicons dashicons-star-empty"></i>';
296 }
297
298 html += '</span>' +
299 '<span class="bdt-others-plugin-rating-text bdt-margin-small-left">' +
300 rating + ' <?php echo esc_js(__('out of 5 stars.', 'ultimate-post-kit')); ?>';
301
302 var numRatings = Number(plugin.num_ratings) || 0;
303 if (numRatings > 0) {
304 html += '<span class="rating-count">(' + upkEsc(numRatings.toLocaleString()) + ' <?php echo esc_js(__('ratings', 'ultimate-post-kit')); ?>)</span>';
305 }
306
307 html += '</span></div>';
308
309 // Downloads
310 if (plugin.downloaded_formatted) {
311 html += '<div class="bdt-others-plugin-downloads bdt-margin-small-top">' +
312 '<span><?php echo esc_js(__('Downloads: ', 'ultimate-post-kit')); ?>' + upkEsc(plugin.downloaded_formatted) + '</span>' +
313 '</div>';
314 }
315
316 // Last updated
317 if (plugin.last_updated_formatted) {
318 html += '<div class="bdt-others-plugin-updated bdt-margin-small-top">' +
319 '<span><?php echo esc_js(__('Last Updated: ', 'ultimate-post-kit')); ?>' + upkEsc(plugin.last_updated_formatted) + '</span>' +
320 '</div>';
321 }
322
323 html += '</div></div>' +
324 '<div class="bdt-others-plugins-link">';
325
326 // Show different buttons based on plugin status
327 if (plugin.status === 'active') {
328 html += '<span class="bdt-button bdt-button-success bdt-disabled">' +
329 '<span class="dashicons dashicons-yes"></span> ' +
330 '<?php echo esc_js(__('Active', 'ultimate-post-kit')); ?>' +
331 '</span>';
332 } else if (plugin.status === 'installed') {
333 // URL-encode the query values: plugin_file contains slashes and
334 // both parts land inside an href attribute.
335 var activateUrl = <?php echo wp_json_encode( esc_url_raw( admin_url( 'plugins.php?action=activate&plugin=' ) ) ); ?> +
336 encodeURIComponent(plugin.plugin_file || '') +
337 '&_wpnonce=' + encodeURIComponent(plugin.activate_nonce || '');
338 html += '<a class="bdt-button bdt-welcome-button" href="' + upkEsc(activateUrl) + '">' +
339 '<?php echo esc_js(__('Activate', 'ultimate-post-kit')); ?>' +
340 '</a>';
341 } else {
342 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') ); ?>">' +
343 '<?php echo esc_js(__('Install', 'ultimate-post-kit')); ?>' +
344 '</button>';
345 }
346
347 if (plugin.homepage && upkSafeUrl(plugin.homepage)) {
348 html += '<a class="bdt-button bdt-dashboard-sec-btn" target="_blank" rel="noopener noreferrer" href="' + upkEsc(upkSafeUrl(plugin.homepage)) + '">' +
349 '<?php echo esc_js(__('Learn More', 'ultimate-post-kit')); ?>' +
350 '</a>';
351 }
352
353 html += '</div></div>';
354 });
355 }
356
357 $list.html(html);
358
359 // Handle plugin action buttons. Delegated from the list and
360 // namespaced+unbound first: renderPlugins() runs again on every
361 // retry, and a plain global bind stacked one handler per render,
362 // firing duplicate install requests for a single click.
363 $list.off('click.upkInstall').on('click.upkInstall', '.upk-install-plugin', function(e) {
364 e.preventDefault();
365
366 var $button = $(this);
367 var pluginSlug = $button.data('plugin-slug');
368 var nonce = $button.data('nonce');
369 var originalText = $button.text();
370
371 // Disable button and show loading state
372 $button.prop('disabled', true)
373 .text('<?php echo esc_js(__('Installing...', 'ultimate-post-kit')); ?>')
374 .addClass('bdt-installing');
375
376 // Perform AJAX request
377 $.ajax({
378 url: '<?php echo esc_url( admin_url('admin-ajax.php') ); ?>',
379 type: 'POST',
380 data: {
381 action: 'upk_install_plugin',
382 plugin_slug: pluginSlug,
383 nonce: nonce
384 },
385 success: function(response) {
386 if (response.success) {
387 // Show success message
388 $button.text('<?php echo esc_js(__('Installed!', 'ultimate-post-kit')); ?>')
389 .removeClass('bdt-installing')
390 .addClass('bdt-installed');
391
392 // Show success notification
393 if (typeof bdtUIkit !== 'undefined' && bdtUIkit.notification) {
394 bdtUIkit.notification({
395 message: '<span class="dashicons dashicons-yes"></span> ' + response.data.message,
396 status: 'success'
397 });
398 }
399
400 // Reload the page after 2 seconds to update button states
401 setTimeout(function() {
402 window.location.reload();
403 }, 2000);
404
405 } else {
406 // Show error message
407 $button.prop('disabled', false)
408 .text(originalText)
409 .removeClass('bdt-installing');
410
411 // Show error notification
412 if (typeof bdtUIkit !== 'undefined' && bdtUIkit.notification) {
413 bdtUIkit.notification({
414 message: '<span class="dashicons dashicons-warning"></span> ' + response.data.message,
415 status: 'danger'
416 });
417 } else {
418 alert('Error: ' + response.data.message);
419 }
420 }
421 },
422 error: function(xhr, status, error) {
423 // Show error message
424 $button.prop('disabled', false)
425 .text(originalText)
426 .removeClass('bdt-installing');
427
428 // Show error notification
429 if (typeof bdtUIkit !== 'undefined' && bdtUIkit.notification) {
430 bdtUIkit.notification({
431 message: '<span class="dashicons dashicons-warning"></span> <?php echo esc_js(__('Installation failed. Please try again.', 'ultimate-post-kit')); ?>',
432 status: 'danger'
433 });
434 } else {
435 alert('<?php echo esc_js(__('Installation failed. Please try again.', 'ultimate-post-kit')); ?>');
436 }
437 }
438 });
439 });
440 }
441
442 // Function to show loading state
443 function showLoading() {
444 $list.html(
445 '<div class="bdt-text-center bdt-padding-large">' +
446 '<div class="upk-loading-spinner">' +
447 '<div class="upk-loading-dots">' +
448 '<div class="upk-loading-dot"></div>' +
449 '<div class="upk-loading-dot"></div>' +
450 '<div class="upk-loading-dot"></div>' +
451 '</div>' +
452 '</div>' +
453 '<p class="bdt-margin-small-top bdt-text-muted"><?php echo esc_js(__('Loading plugin data...', 'ultimate-post-kit')); ?></p>' +
454 '</div>'
455 );
456 // Set the display explicitly: the list is a CSS grid, and jQuery's
457 // .show() can resolve the inline display:none to "block", which would
458 // flatten the card grid into a single stacked column.
459 $list.css('display', 'grid');
460 }
461
462 // Function to show error
463 function showError() {
464 $error.show();
465 $list.hide();
466 }
467
468 // Retry button handler
469 $('#upk-retry-load-plugins').on('click', function() {
470 loadPlugins();
471 });
472
473 // Initial load
474 loadPlugins();
475 });
476 </script>
477 <?php
478 }
479
480 /**
481 * AJAX handler for getting plugins data
482 */
483 public function ajax_get_plugins() {
484 // Verify nonce. Respond with JSON -- the caller parses the response as
485 // JSON, so wp_die() here would surface as a generic "unable to load".
486 if (!check_ajax_referer('upk_get_plugins_nonce', 'nonce', false)) {
487 wp_send_json_error(['message' => __('Security check failed.', 'ultimate-post-kit')], 403);
488 }
489
490 // This data is only ever used on the plugin-install screen; gate it to
491 // users who could act on it rather than exposing it to any visitor.
492 if (!current_user_can('install_plugins')) {
493 wp_send_json_error(['message' => __('You do not have permission to do this.', 'ultimate-post-kit')], 403);
494 }
495
496 // Get cached data
497 $plugins_data = \UltimatePostKit\SetupWizard\Remote_Data_Handler::get_remote_plugins();
498
499 // If cache is empty, try to fetch immediately (but don't block)
500 if (empty($plugins_data)) {
501 // Schedule background fetch if not already done
502 \UltimatePostKit\SetupWizard\Remote_Data_Handler::schedule_remote_fetch();
503
504 // Return empty response with flag indicating data is loading
505 wp_send_json_success([
506 'plugins' => [],
507 'loading' => true,
508 'message' => __('Loading plugin data...', 'ultimate-post-kit')
509 ]);
510 }
511
512 // Send response
513 wp_send_json_success([
514 'plugins' => $plugins_data,
515 'loading' => false,
516 'message' => __('Plugin data loaded successfully.', 'ultimate-post-kit')
517 ]);
518 }
519
520 /**
521 * AJAX handler for plugin installation
522 */
523 public function install_plugin_ajax() {
524 // Check nonce
525 $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
526 if (!wp_verify_nonce( $nonce, 'upk_install_plugin_nonce')) {
527 wp_send_json_error(['message' => __('Security check failed', 'ultimate-post-kit')]);
528 }
529
530 // Check user capability
531 if (!current_user_can('install_plugins')) {
532 wp_send_json_error(['message' => __('You do not have permission to install plugins', 'ultimate-post-kit')]);
533 }
534
535 $plugin_slug = isset($_POST['plugin_slug']) ? sanitize_text_field(wp_unslash($_POST['plugin_slug'])) : '';
536
537 if (empty($plugin_slug)) {
538 wp_send_json_error(['message' => __('Plugin slug is required', 'ultimate-post-kit')]);
539 }
540
541 // Include necessary WordPress files
542 require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
543 require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
544 require_once ABSPATH . 'wp-admin/includes/class-wp-ajax-upgrader-skin.php';
545
546 // Get plugin information
547 $api = plugins_api('plugin_information', [
548 'slug' => $plugin_slug,
549 'fields' => [
550 'sections' => false,
551 ],
552 ]);
553
554 if (is_wp_error($api)) {
555 wp_send_json_error(['message' => __('Plugin not found: ', 'ultimate-post-kit') . $api->get_error_message()]);
556 }
557
558 // Install the plugin
559 $skin = new \WP_Ajax_Upgrader_Skin();
560 $upgrader = new \Plugin_Upgrader($skin);
561 $result = $upgrader->install($api->download_link);
562
563 if (is_wp_error($result)) {
564 wp_send_json_error(['message' => __('Installation failed: ', 'ultimate-post-kit') . $result->get_error_message()]);
565 } elseif ($skin->get_errors()->has_errors()) {
566 wp_send_json_error(['message' => __('Installation failed: ', 'ultimate-post-kit') . $skin->get_error_messages()]);
567 } elseif (is_null($result)) {
568 wp_send_json_error(['message' => __('Installation failed: Unable to connect to filesystem', 'ultimate-post-kit')]);
569 }
570
571 // Get installation status
572 $install_status = install_plugin_install_status($api);
573
574 wp_send_json_success([
575 'message' => __('Plugin installed successfully!', 'ultimate-post-kit'),
576 'plugin_file' => $install_status['file'],
577 'plugin_name' => $api->name
578 ]);
579 }
580 }
581
582 // Initialize the manager
583 new UltimatePostKit_Others_Plugin_Manager();
584
585 /**
586 * Helper function for easy rendering
587 */
588 function ultimate_post_kit_others_plugin() {
589 $manager = new UltimatePostKit_Others_Plugin_Manager();
590 $manager->render_others_plugin();
591 }
592