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
← All changes | includes/setup-wizard/ultimate-post-kit-others-plugin.php +82 -81 4.2.0 → 4.5.6 View file →
@@ -21,11 +21,11 @@
21 21 /**
22 22 * Constructor
23 23 */
24 24 public function __construct() {
25 - // Add AJAX handlers
25 + // Add AJAX handlers. This is an admin-only, plugin-install screen; the
26 + // handler must never be exposed to unauthenticated visitors.
26 27 add_action('wp_ajax_upk_get_plugins', [$this, 'ajax_get_plugins']);
27 - add_action('wp_ajax_nopriv_upk_get_plugins', [$this, 'ajax_get_plugins']);
28 28 add_action('wp_ajax_upk_install_plugin', [$this, 'install_plugin_ajax']);
29 29 }
30 30
31 31 /**
@@ -54,8 +54,9 @@
54 54 );
55 55
56 56 // Helper function for time formatting
57 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.
58 59 function format_last_updated_usk($date_string) {
59 60 if (empty($date_string)) {
60 61 return __('Unknown', 'ultimate-post-kit');
61 62 }
@@ -92,41 +93,8 @@
92 93 }
93 94 }
94 95 }
95 96
96 - // Helper function for fallback URLs
97 - if (!function_exists('get_plugin_fallback_urls_usk')) {
98 - function get_plugin_fallback_urls_usk($plugin_slug) {
99 - // Handle different plugin slug formats
100 - if (strpos($plugin_slug, '/') !== false) {
101 - // If it's a file path like 'plugin-name/plugin-name.php', extract directory
102 - $plugin_slug_clean = dirname($plugin_slug);
103 - } else {
104 - // If it's just the plugin directory name, use it directly
105 - $plugin_slug_clean = $plugin_slug;
106 - }
107 -
108 - // Custom icon URLs for specific plugins that might not be on WordPress.org
109 - $custom_icons = [
110 - 'ar-viewer' => [
111 - 'https://ps.w.org/ar-viewer/assets/icon-256x256.gif',
112 - 'https://ps.w.org/ar-viewer/assets/icon-128x128.gif',
113 - ],
114 - ];
115 -
116 - // Return custom icons if available, otherwise use default WordPress.org URLs
117 - if (isset($custom_icons[$plugin_slug_clean])) {
118 - return $custom_icons[$plugin_slug_clean];
119 - }
120 -
121 - return [
122 - "https://ps.w.org/{$plugin_slug_clean}/assets/icon-256x256.png", // Then PNG
123 - "https://ps.w.org/{$plugin_slug_clean}/assets/icon-128x128.png", // Medium PNG
124 - "https://ps.w.org/{$plugin_slug_clean}/assets/icon-256x256.gif", // Try GIF first
125 - "https://ps.w.org/{$plugin_slug_clean}/assets/icon-128x128.gif", // Medium GIF
126 - ];
127 - }
128 - }
129 97 ?>
130 98
131 99 <div class="upk-dashboard-panel"
132 100 bdt-scrollspy="target: > div > div > .bdt-card; cls: bdt-animation-slide-bottom-small; delay: 300">
@@ -244,14 +212,28 @@
244 212 }
245 213 });
246 214 }
247 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 +
248 230 // Function to render plugins
249 231 function renderPlugins(plugins) {
250 232 var html = '';
251 233
252 234 if (plugins.length === 0) {
253 - html = '<div class="bdt-text-center bdt-padding-large"><p><?php esc_html_e('No plugins available.', 'ultimate-post-kit'); ?></p></div>';
235 + html = '<div class="bdt-text-center bdt-padding-large"><p><?php echo esc_js(__('No plugins available.', 'ultimate-post-kit')); ?></p></div>';
254 236 } else {
255 237 plugins.forEach(function(plugin) {
256 238 // Skip own plugin (Ultimate Post Kit) when printing only; data still includes it for other plugins
257 239 if (plugin.slug === 'ultimate-post-kit') return;
@@ -259,39 +241,37 @@
259 241 var logoUrl = plugin.logo || '';
260 242 var pluginName = plugin.name || '';
261 243 var pluginSlug = plugin.slug || '';
262 244
263 - // Generate fallback logo URL if needed
264 - if (!logoUrl) {
265 - var actualSlug = pluginSlug.replace('.php', '').split('/')[0];
266 - logoUrl = 'https://ps.w.org/' + actualSlug + '/assets/icon-256x256.png';
267 - }
268 -
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 +
269 254 html += '<div class="bdt-card bdt-card-body bdt-flex bdt-flex-middle bdt-flex-between">' +
270 255 '<div class="bdt-others-plugin-content">' +
271 256 '<div class="bdt-plugin-logo-wrap bdt-flex bdt-flex-middle">' +
272 257 '<div class="bdt-plugin-logo-container">' +
273 - '<img src="' + logoUrl + '" alt="' + pluginName + '" class="bdt-plugin-logo" ' +
274 - 'onerror="this.style.display=\'none\'; this.nextElementSibling.style.display=\'flex\';">' +
275 - '<div class="default-plugin-icon" style="display:none;">📦</div>' +
258 + logoMarkup +
276 259 '</div>' +
277 260 '<div class="bdt-others-plugin-user-wrap bdt-flex bdt-flex-middle">' +
278 - '<h1 class="upk-feature-title">' + pluginName + '</h1>' +
261 + '<h1 class="upk-feature-title">' + upkEsc(pluginName) + '</h1>' +
279 262 '</div>' +
280 263 '</div>' +
281 264 '<div class="bdt-others-plugin-content-text bdt-margin-top">';
282 -
283 - if (plugin.description) {
284 - html += '<p>' + plugin.description + '</p>';
285 - }
286 -
265 +
287 266 // Active installs
267 + var installsCount = Number(plugin.active_installs_count) || 0;
288 268 html += '<span class="active-installs bdt-margin-small-top">' +
289 - '<?php esc_html_e("Active Installs: ", "ultimate-post-kit"); ?> ';
290 - if (plugin.active_installs_count > 0) {
291 - html += '<span class="installs-count">' + plugin.active_installs_count.toLocaleString() + '+</span>';
269 + '<?php echo esc_js(__('Active Installs: ', 'ultimate-post-kit')); ?> ';
270 + if (installsCount > 0) {
271 + html += '<span class="installs-count">' + upkEsc(installsCount.toLocaleString()) + '+</span>';
292 272 } else {
293 - html += '<span class="installs-count">Fewer than 10</span>';
273 + html += '<span class="installs-count"><?php echo esc_js(__('Fewer than 10', 'ultimate-post-kit')); ?></span>';
294 274 }
295 275 html += '</span>';
296 276
297 277 // Rating
@@ -297,29 +277,32 @@
297 277 // Rating
298 278 html += '<div class="bdt-others-plugin-rating bdt-margin-small-top bdt-flex bdt-flex-middle">' +
299 279 '<span class="bdt-others-plugin-rating-stars">';
300 280
301 - var rating = parseFloat(plugin.rating) || 0;
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));
302 283 var fullStars = Math.floor(rating);
303 284 var hasHalfStar = (rating - fullStars) >= 0.5;
304 285 var emptyStars = 5 - fullStars - (hasHalfStar ? 1 : 0);
305 -
306 - for (var i = 0; i < fullStars; i++) {
286 + var i;
287 +
288 + for (i = 0; i < fullStars; i++) {
307 289 html += '<i class="dashicons dashicons-star-filled"></i>';
308 290 }
309 291 if (hasHalfStar) {
310 292 html += '<i class="dashicons dashicons-star-half"></i>';
311 293 }
312 - for (var i = 0; i < emptyStars; i++) {
294 + for (i = 0; i < emptyStars; i++) {
313 295 html += '<i class="dashicons dashicons-star-empty"></i>';
314 296 }
315 297
316 298 html += '</span>' +
317 299 '<span class="bdt-others-plugin-rating-text bdt-margin-small-left">' +
318 - rating + ' <?php esc_html_e("out of 5 stars.", "ultimate-post-kit"); ?>';
300 + rating + ' <?php echo esc_js(__('out of 5 stars.', 'ultimate-post-kit')); ?>';
319 301
320 - if (plugin.num_ratings > 0) {
321 - html += '<span class="rating-count">(' + plugin.num_ratings.toLocaleString() + ' <?php esc_html_e("ratings", "ultimate-post-kit"); ?>)</span>';
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>';
322 305 }
323 306
324 307 html += '</span></div>';
325 308
@@ -325,9 +308,9 @@
325 308
326 309 // Downloads
327 310 if (plugin.downloaded_formatted) {
328 311 html += '<div class="bdt-others-plugin-downloads bdt-margin-small-top">' +
329 - '<span><?php esc_html_e("Downloads: ", "ultimate-post-kit"); ?>' + plugin.downloaded_formatted + '</span>' +
312 + '<span><?php echo esc_js(__('Downloads: ', 'ultimate-post-kit')); ?>' + upkEsc(plugin.downloaded_formatted) + '</span>' +
330 313 '</div>';
331 314 }
332 315
333 316 // Last updated
@@ -332,9 +315,9 @@
332 315
333 316 // Last updated
334 317 if (plugin.last_updated_formatted) {
335 318 html += '<div class="bdt-others-plugin-updated bdt-margin-small-top">' +
336 - '<span><?php esc_html_e("Last Updated: ", "ultimate-post-kit"); ?>' + plugin.last_updated_formatted + '</span>' +
319 + '<span><?php echo esc_js(__('Last Updated: ', 'ultimate-post-kit')); ?>' + upkEsc(plugin.last_updated_formatted) + '</span>' +
337 320 '</div>';
338 321 }
339 322
340 323 html += '</div></div>' +
@@ -343,24 +326,28 @@
343 326 // Show different buttons based on plugin status
344 327 if (plugin.status === 'active') {
345 328 html += '<span class="bdt-button bdt-button-success bdt-disabled">' +
346 329 '<span class="dashicons dashicons-yes"></span> ' +
347 - '<?php esc_html_e("Active", "ultimate-post-kit"); ?>' +
330 + '<?php echo esc_js(__('Active', 'ultimate-post-kit')); ?>' +
348 331 '</span>';
349 332 } else if (plugin.status === 'installed') {
350 - var activateUrl = '<?php echo esc_url( admin_url("plugins.php?action=activate&plugin=") ); ?>' + plugin.plugin_file + '&_wpnonce=' + plugin.activate_nonce;
351 - html += '<a class="bdt-button bdt-welcome-button" href="' + activateUrl + '">' +
352 - '<?php esc_html_e("Activate", "ultimate-post-kit"); ?>' +
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')); ?>' +
353 340 '</a>';
354 341 } else {
355 - html += '<button class="bdt-button bdt-welcome-button upk-install-plugin" data-plugin-slug="' + pluginSlug + '" data-nonce="<?php echo esc_attr( wp_create_nonce('upk_install_plugin_nonce') ); ?>">' +
356 - '<?php esc_html_e("Install", "ultimate-post-kit"); ?>' +
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')); ?>' +
357 344 '</button>';
358 345 }
359 346
360 - if (plugin.homepage) {
361 - html += '<a class="bdt-button bdt-dashboard-sec-btn" target="_blank" href="' + plugin.homepage + '">' +
362 - '<?php esc_html_e("Learn More", "ultimate-post-kit"); ?>' +
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')); ?>' +
363 350 '</a>';
364 351 }
365 352
366 353 html += '</div></div>';
@@ -368,10 +355,13 @@
368 355 }
369 356
370 357 $list.html(html);
371 358
372 - // Handle plugin action buttons
373 - $('.upk-install-plugin').on('click', function(e) {
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) {
374 364 e.preventDefault();
375 365
376 366 var $button = $(this);
377 367 var pluginSlug = $button.data('plugin-slug');
@@ -459,12 +449,15 @@
459 449 '<div class="upk-loading-dot"></div>' +
460 450 '<div class="upk-loading-dot"></div>' +
461 451 '</div>' +
462 452 '</div>' +
463 - '<p class="bdt-margin-small-top bdt-text-muted"><?php esc_html_e("Loading plugin data...", "ultimate-post-kit"); ?></p>' +
453 + '<p class="bdt-margin-small-top bdt-text-muted"><?php echo esc_js(__('Loading plugin data...', 'ultimate-post-kit')); ?></p>' +
464 454 '</div>'
465 455 );
466 - $list.show();
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');
467 460 }
468 461
469 462 // Function to show error
470 463 function showError() {
@@ -487,13 +480,20 @@
487 480 /**
488 481 * AJAX handler for getting plugins data
489 482 */
490 483 public function ajax_get_plugins() {
491 - // Verify nonce
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".
492 486 if (!check_ajax_referer('upk_get_plugins_nonce', 'nonce', false)) {
493 - wp_die(esc_html__('Security check failed.', 'ultimate-post-kit'));
487 + wp_send_json_error(['message' => __('Security check failed.', 'ultimate-post-kit')], 403);
494 488 }
495 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 496 // Get cached data
497 497 $plugins_data = \UltimatePostKit\SetupWizard\Remote_Data_Handler::get_remote_plugins();
498 498
499 499 // If cache is empty, try to fetch immediately (but don't block)
@@ -521,9 +521,10 @@
521 521 * AJAX handler for plugin installation
522 522 */
523 523 public function install_plugin_ajax() {
524 524 // Check nonce
525 - if (!wp_verify_nonce($_POST['nonce'], 'upk_install_plugin_nonce')) {
525 + $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
526 + if (!wp_verify_nonce( $nonce, 'upk_install_plugin_nonce')) {
526 527 wp_send_json_error(['message' => __('Security check failed', 'ultimate-post-kit')]);
527 528 }
528 529
529 530 // Check user capability
@@ -530,9 +531,9 @@
530 531 if (!current_user_can('install_plugins')) {
531 532 wp_send_json_error(['message' => __('You do not have permission to install plugins', 'ultimate-post-kit')]);
532 533 }
533 534
534 - $plugin_slug = sanitize_text_field($_POST['plugin_slug']);
535 + $plugin_slug = isset($_POST['plugin_slug']) ? sanitize_text_field(wp_unslash($_POST['plugin_slug'])) : '';
535 536
536 537 if (empty($plugin_slug)) {
537 538 wp_send_json_error(['message' => __('Plugin slug is required', 'ultimate-post-kit')]);
538 539 }