| 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 { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[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 |
|