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/views/integration.php +71 -57 4.1.1 → 4.5.6 View file →
@@ -8,8 +8,10 @@
8 8 if (!defined('ABSPATH')) {
9 9 exit;
10 10 }
11 11
12 +// phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- template partial included within a method; variables are method-scoped, not global.
13 +
12 14 // Include the required classes
13 15 require_once __DIR__ . '/../class-plugin-integration-helper.php';
14 16 require_once __DIR__ . '/../class-remote-data-handler.php';
15 17
@@ -30,20 +32,25 @@
30 32 if ($diff < 60) {
31 33 return __('Just now', 'ultimate-post-kit');
32 34 } elseif ($diff < 3600) {
33 35 $minutes = floor($diff / 60);
36 + /* translators: %d: number of minutes */
34 37 return sprintf(_n('%d minute ago', '%d minutes ago', $minutes, 'ultimate-post-kit'), $minutes);
35 38 } elseif ($diff < 86400) {
36 39 $hours = floor($diff / 3600);
40 + /* translators: %d: number of hours */
37 41 return sprintf(_n('%d hour ago', '%d hours ago', $hours, 'ultimate-post-kit'), $hours);
38 42 } elseif ($diff < 2592000) { // 30 days
39 43 $days = floor($diff / 86400);
44 + /* translators: %d: number of days */
40 45 return sprintf(_n('%d day ago', '%d days ago', $days, 'ultimate-post-kit'), $days);
41 46 } elseif ($diff < 31536000) { // 1 year
42 47 $months = floor($diff / 2592000);
48 + /* translators: %d: number of months */
43 49 return sprintf(_n('%d month ago', '%d months ago', $months, 'ultimate-post-kit'), $months);
44 50 } else {
45 51 $years = floor($diff / 31536000);
52 + /* translators: %d: number of years */
46 53 return sprintf(_n('%d year ago', '%d years ago', $years, 'ultimate-post-kit'), $years);
47 54 }
48 55 }
49 56 }
@@ -48,38 +55,8 @@
48 55 }
49 56 }
50 57
51 58 // Helper function for fallback URLs
52 -if (!function_exists('get_plugin_fallback_urls_usk')) {
53 - function get_plugin_fallback_urls_usk($plugin_slug) {
54 - // Handle different plugin slug formats
55 - if (strpos($plugin_slug, '/') !== false) {
56 - // If it's a file path like 'plugin-name/plugin-name.php', extract directory
57 - $plugin_slug_clean = dirname($plugin_slug);
58 - } else {
59 - // If it's just the plugin directory name, use it directly
60 - $plugin_slug_clean = $plugin_slug;
61 - }
62 -
63 - // Custom icon URLs for specific plugins that might not be on WordPress.org
64 - $custom_icons = [
65 - 'ar-viewer' => [
66 - 'https://ps.w.org/ar-viewer/assets/icon-256x256.gif',
67 - 'https://ps.w.org/ar-viewer/assets/icon-128x128.gif',
68 - ],
69 - ];
70 -
71 - // Return custom icons if available, otherwise use default WordPress.org URLs
72 - if (isset($custom_icons[$plugin_slug_clean])) {
73 - return $custom_icons[$plugin_slug_clean];
74 - }
75 -
76 - return [
77 - "https://ps.w.org/{$plugin_slug_clean}/assets/icon-256x256.png", // Large PNG
78 - "https://ps.w.org/{$plugin_slug_clean}/assets/icon-128x128.png", // Medium PNG
79 - ];
80 - }
81 -}
82 59
83 60 // Define plugin slugs
84 61 $plugin_slugs = array(
85 62 'bdthemes-element-pack-lite',
@@ -177,13 +154,10 @@
177 154 echo '<img src="' . esc_url($logo_url) . '" alt="' . esc_attr($plugin_name) . '" onerror="this.style.display=\'none\'; this.nextElementSibling.style.display=\'flex\';">';
178 155 echo '<div class="default-plugin-icon" style="display:none;">📦</div>';
179 156 } else {
180 157 // Generate fallback URLs for WordPress.org
181 - $actual_slug = (strpos($plugin_slug, '/') !== false) ? dirname($plugin_slug) : $plugin_slug;
182 - $fallback_urls = get_plugin_fallback_urls_usk($actual_slug);
183 -
184 - echo '<img src="' . esc_url($fallback_urls[0]) . '" alt="' . esc_attr($plugin_name) . '" onerror="this.style.display=\'none\'; this.nextElementSibling.style.display=\'flex\';">';
185 - echo '<div class="default-plugin-icon" style="display:none;">📦</div>';
158 + // No icon in the API response, show the local placeholder.
159 + echo '<div class="default-plugin-icon" style="display:flex;">📦</div>';
186 160 }
187 161 ?>
188 162 </span>
189 163
@@ -199,9 +173,9 @@
199 173 <?php
200 174 if (!$is_active) : ?>
201 175 <label class="switch">
202 176 <input type="checkbox" class="plugin-slider-checkbox" <?php echo $plugin_recommended ? 'checked' : ''; ?>
203 - name="plugins[]<?php echo isset($plugin['slug']) ? wp_kses_post($plugin['slug']) : ''; ?>">
177 + name="plugins[]<?php echo isset($plugin['slug']) ? esc_attr($plugin['slug']) : ''; ?>">
204 178 <span class="slider round"></span>
205 179 </label>
206 180 <?php
207 181 endif;
@@ -209,9 +183,9 @@
209 183 </div>
210 184 </span>
211 185 <div class="bdt-flex bdt-flex-middle">
212 186 <span class="bdt-plugin-name">
213 - <?php echo wp_kses_post($plugin['name']); ?>
187 + <?php echo esc_html($plugin['name']); ?>
214 188 </span>
215 189 </div>
216 190
217 191 <span class="active-installs">
@@ -224,9 +198,9 @@
224 198 ?>
225 199 </span>
226 200
227 201 <?php if (isset($plugin['downloaded_formatted']) && !empty($plugin['downloaded_formatted'])): ?>
228 - <span class="downloads"><?php esc_html_e('Downloads: ', 'ultimate-post-kit'); echo wp_kses_post($plugin['downloaded_formatted']); ?></span>
202 + <span class="downloads"><?php esc_html_e('Downloads: ', 'ultimate-post-kit'); echo esc_html($plugin['downloaded_formatted']); ?></span>
229 203 <?php endif; ?>
230 204
231 205 <div class="rating-section">
232 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);">
@@ -349,9 +323,9 @@
349 323 url: ajaxurl,
350 324 type: 'POST',
351 325 data: {
352 326 action: 'upk_get_plugins',
353 - nonce: '<?php echo wp_create_nonce('upk_get_plugins_nonce'); ?>'
327 + nonce: '<?php echo esc_attr( wp_create_nonce('upk_get_plugins_nonce') ); ?>'
354 328 },
355 329 success: function(response) {
356 330 if (response.success && response.data.plugins) {
357 331 // Hide the initial loading div
@@ -367,8 +341,33 @@
367 341 }
368 342 });
369 343 }
370 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 +
371 370 // Function to render plugin list
372 371 function renderPluginList(plugins) {
373 372 const $pluginList = $('#upk-integration-plugin-list');
374 373 let html = '';
@@ -373,9 +372,9 @@
373 372 const $pluginList = $('#upk-integration-plugin-list');
374 373 let html = '';
375 374
376 375 if (plugins.length === 0) {
377 - html = '<div class="upk-no-plugins" style="text-align: center; padding: 40px;"><p>No plugins found.</p></div>';
376 + html = `<div class="upk-no-plugins" style="text-align: center; padding: 40px;"><p>${upkEsc(upkI18n.noPlugins)}</p></div>`;
378 377 } else {
379 378 plugins.forEach(function(plugin) {
380 379 // Skip own plugin (Ultimate Post Kit) when printing only; data still includes it for other plugins
381 380 if (plugin.slug === 'ultimate-post-kit') return;
@@ -382,19 +381,19 @@
382 381 const isActive = plugin.status === 'active';
383 382 const isRecommended = plugin.recommended && !isActive;
384 383
385 384 html += `
386 - <label class="plugin-item" data-slug="${plugin.slug}">
385 + <label class="plugin-item" data-slug="${upkEsc(plugin.slug)}">
387 386 <span class="bdt-flex bdt-flex-middle bdt-flex-between bdt-margin-small-bottom">
388 387 <span class="bdt-plugin-logo">
389 388 ${generatePluginLogo(plugin)}
390 389 </span>
391 390 <div class="bdt-plugin-badge-switch-wrap">
392 - ${isRecommended ? '<span class="recommended-badge">Recommended</span>' : ''}
393 - ${isActive ? '<span class="active-badge">ACTIVE</span>' : ''}
391 + ${isRecommended ? `<span class="recommended-badge">${upkEsc(upkI18n.recommended)}</span>` : ''}
392 + ${isActive ? `<span class="active-badge">${upkEsc(upkI18n.active)}</span>` : ''}
394 393 ${!isActive ? `
395 394 <label class="switch">
396 - <input type="checkbox" class="plugin-slider-checkbox" ${plugin.recommended ? 'checked' : ''} name="plugins[]${plugin.slug}">
395 + <input type="checkbox" class="plugin-slider-checkbox" ${plugin.recommended ? 'checked' : ''} name="plugins[]${upkEsc(plugin.slug)}">
397 396 <span class="slider round"></span>
398 397 </label>
399 398 ` : ''}
400 399 </div>
@@ -399,25 +398,24 @@
399 398 ` : ''}
400 399 </div>
401 400 </span>
402 401 <div class="bdt-flex bdt-flex-middle">
403 - <span class="bdt-plugin-name">${plugin.name}</span>
402 + <span class="bdt-plugin-name">${upkEsc(plugin.name)}</span>
404 403 </div>
405 404 <span class="active-installs">
406 - Active Installs:
407 - <span class="installs-count">${plugin.active_installs_count > 0 ? plugin.active_installs_count.toLocaleString() + '+' : 'Fewer than 10'}</span>
405 + ${upkFormat(upkEsc(upkI18n.activeInstalls), `<span class="installs-count">${plugin.active_installs_count > 0 ? upkEsc(plugin.active_installs_count.toLocaleString() + '+') : upkEsc(upkI18n.fewerThanTen)}</span>`)}
408 406 </span>
409 - ${plugin.downloaded_formatted ? `<span class="downloads">Downloads: ${plugin.downloaded_formatted}</span>` : ''}
407 + ${plugin.downloaded_formatted ? `<span class="downloads">${upkFormat(upkEsc(upkI18n.downloads), upkEsc(plugin.downloaded_formatted))}</span>` : ''}
410 408 <div class="rating-section">
411 - <div class="wporg-ratings" title="${plugin.rating} out of 5 stars" style="color:var(--wp--preset--color--pomegrade-1, #e26f56);">
409 + <div class="wporg-ratings" title="${upkEsc(upkFormat(upkI18n.ratingTitle, plugin.rating))}" style="color:var(--wp--preset--color--pomegrade-1, #e26f56);">
412 410 ${generateStarRating(plugin.rating)}
413 411 </div>
414 412 <span class="rating-text">
415 - ${plugin.rating} out of 5 stars.
416 - ${plugin.num_ratings > 0 ? `<span class="rating-count">(${plugin.num_ratings.toLocaleString()} ratings)</span>` : ''}
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>` : ''}
417 415 </span>
418 416 </div>
419 - ${plugin.last_updated_formatted ? `<span class="last-updated">Last Updated: ${plugin.last_updated_formatted}</span>` : ''}
417 + ${plugin.last_updated_formatted ? `<span class="last-updated">${upkFormat(upkEsc(upkI18n.lastUpdated), upkEsc(plugin.last_updated_formatted))}</span>` : ''}
420 418 </label>
421 419 `;
422 420 });
423 421 }
@@ -424,17 +422,33 @@
424 422
425 423 $pluginList.html(html);
426 424 }
427 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 +
428 442 // Helper function to generate plugin logo
429 443 function generatePluginLogo(plugin) {
430 444 if (plugin.logo && plugin.logo.match(/^https?:\/\//)) {
431 - return `<img src="${plugin.logo}" alt="${plugin.name}" onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
445 + return `<img src="${upkEsc(upkSafeUrl(plugin.logo))}" alt="${upkEsc(plugin.name)}" onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
432 446 <div class="default-plugin-icon" style="display:none;">📦</div>`;
433 447 } else {
434 - const slug = plugin.slug.includes('/') ? plugin.slug.split('/')[0] : plugin.slug;
435 - return `<img src="https://ps.w.org/${slug}/assets/icon-256x256.png" alt="${plugin.name}" onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
436 - <div class="default-plugin-icon" style="display:none;">📦</div>`;
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>`;
437 451 }
438 452 }
439 453
440 454 // Helper function to generate star rating
@@ -466,9 +480,9 @@
466 480
467 481 // Show error in plugin list
468 482 $pluginList.html(`
469 483 <div class="upk-error-state" style="text-align: center; padding: 40px;">
470 - <p style="color: #d63638;">${message}</p>
484 + <p style="color: #d63638;">${upkEsc(message)}</p>
471 485 <button type="button" class="bdt-button bdt-button-secondary" onclick="location.reload()">Retry</button>
472 486 </div>
473 487 `);
474 488 }