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 +65 -56 4.2.3 → 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
@@ -53,38 +55,8 @@
53 55 }
54 56 }
55 57
56 58 // Helper function for fallback URLs
57 -if (!function_exists('get_plugin_fallback_urls_usk')) {
58 - function get_plugin_fallback_urls_usk($plugin_slug) {
59 - // Handle different plugin slug formats
60 - if (strpos($plugin_slug, '/') !== false) {
61 - // If it's a file path like 'plugin-name/plugin-name.php', extract directory
62 - $plugin_slug_clean = dirname($plugin_slug);
63 - } else {
64 - // If it's just the plugin directory name, use it directly
65 - $plugin_slug_clean = $plugin_slug;
66 - }
67 -
68 - // Custom icon URLs for specific plugins that might not be on WordPress.org
69 - $custom_icons = [
70 - 'ar-viewer' => [
71 - 'https://ps.w.org/ar-viewer/assets/icon-256x256.gif',
72 - 'https://ps.w.org/ar-viewer/assets/icon-128x128.gif',
73 - ],
74 - ];
75 -
76 - // Return custom icons if available, otherwise use default WordPress.org URLs
77 - if (isset($custom_icons[$plugin_slug_clean])) {
78 - return $custom_icons[$plugin_slug_clean];
79 - }
80 -
81 - return [
82 - "https://ps.w.org/{$plugin_slug_clean}/assets/icon-256x256.png", // Large PNG
83 - "https://ps.w.org/{$plugin_slug_clean}/assets/icon-128x128.png", // Medium PNG
84 - ];
85 - }
86 -}
87 59
88 60 // Define plugin slugs
89 61 $plugin_slugs = array(
90 62 'bdthemes-element-pack-lite',
@@ -182,13 +154,10 @@
182 154 echo '<img src="' . esc_url($logo_url) . '" alt="' . esc_attr($plugin_name) . '" onerror="this.style.display=\'none\'; this.nextElementSibling.style.display=\'flex\';">';
183 155 echo '<div class="default-plugin-icon" style="display:none;">📦</div>';
184 156 } else {
185 157 // Generate fallback URLs for WordPress.org
186 - $actual_slug = (strpos($plugin_slug, '/') !== false) ? dirname($plugin_slug) : $plugin_slug;
187 - $fallback_urls = get_plugin_fallback_urls_usk($actual_slug);
188 -
189 - echo '<img src="' . esc_url($fallback_urls[0]) . '" alt="' . esc_attr($plugin_name) . '" onerror="this.style.display=\'none\'; this.nextElementSibling.style.display=\'flex\';">';
190 - 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>';
191 160 }
192 161 ?>
193 162 </span>
194 163
@@ -204,9 +173,9 @@
204 173 <?php
205 174 if (!$is_active) : ?>
206 175 <label class="switch">
207 176 <input type="checkbox" class="plugin-slider-checkbox" <?php echo $plugin_recommended ? 'checked' : ''; ?>
208 - name="plugins[]<?php echo isset($plugin['slug']) ? wp_kses_post($plugin['slug']) : ''; ?>">
177 + name="plugins[]<?php echo isset($plugin['slug']) ? esc_attr($plugin['slug']) : ''; ?>">
209 178 <span class="slider round"></span>
210 179 </label>
211 180 <?php
212 181 endif;
@@ -214,9 +183,9 @@
214 183 </div>
215 184 </span>
216 185 <div class="bdt-flex bdt-flex-middle">
217 186 <span class="bdt-plugin-name">
218 - <?php echo wp_kses_post($plugin['name']); ?>
187 + <?php echo esc_html($plugin['name']); ?>
219 188 </span>
220 189 </div>
221 190
222 191 <span class="active-installs">
@@ -229,9 +198,9 @@
229 198 ?>
230 199 </span>
231 200
232 201 <?php if (isset($plugin['downloaded_formatted']) && !empty($plugin['downloaded_formatted'])): ?>
233 - <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>
234 203 <?php endif; ?>
235 204
236 205 <div class="rating-section">
237 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);">
@@ -372,8 +341,33 @@
372 341 }
373 342 });
374 343 }
375 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 +
376 370 // Function to render plugin list
377 371 function renderPluginList(plugins) {
378 372 const $pluginList = $('#upk-integration-plugin-list');
379 373 let html = '';
@@ -378,9 +372,9 @@
378 372 const $pluginList = $('#upk-integration-plugin-list');
379 373 let html = '';
380 374
381 375 if (plugins.length === 0) {
382 - 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>`;
383 377 } else {
384 378 plugins.forEach(function(plugin) {
385 379 // Skip own plugin (Ultimate Post Kit) when printing only; data still includes it for other plugins
386 380 if (plugin.slug === 'ultimate-post-kit') return;
@@ -387,19 +381,19 @@
387 381 const isActive = plugin.status === 'active';
388 382 const isRecommended = plugin.recommended && !isActive;
389 383
390 384 html += `
391 - <label class="plugin-item" data-slug="${plugin.slug}">
385 + <label class="plugin-item" data-slug="${upkEsc(plugin.slug)}">
392 386 <span class="bdt-flex bdt-flex-middle bdt-flex-between bdt-margin-small-bottom">
393 387 <span class="bdt-plugin-logo">
394 388 ${generatePluginLogo(plugin)}
395 389 </span>
396 390 <div class="bdt-plugin-badge-switch-wrap">
397 - ${isRecommended ? '<span class="recommended-badge">Recommended</span>' : ''}
398 - ${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>` : ''}
399 393 ${!isActive ? `
400 394 <label class="switch">
401 - <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)}">
402 396 <span class="slider round"></span>
403 397 </label>
404 398 ` : ''}
405 399 </div>
@@ -404,25 +398,24 @@
404 398 ` : ''}
405 399 </div>
406 400 </span>
407 401 <div class="bdt-flex bdt-flex-middle">
408 - <span class="bdt-plugin-name">${plugin.name}</span>
402 + <span class="bdt-plugin-name">${upkEsc(plugin.name)}</span>
409 403 </div>
410 404 <span class="active-installs">
411 - Active Installs:
412 - <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>`)}
413 406 </span>
414 - ${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>` : ''}
415 408 <div class="rating-section">
416 - <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);">
417 410 ${generateStarRating(plugin.rating)}
418 411 </div>
419 412 <span class="rating-text">
420 - ${plugin.rating} out of 5 stars.
421 - ${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>` : ''}
422 415 </span>
423 416 </div>
424 - ${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>` : ''}
425 418 </label>
426 419 `;
427 420 });
428 421 }
@@ -429,17 +422,33 @@
429 422
430 423 $pluginList.html(html);
431 424 }
432 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 +
433 442 // Helper function to generate plugin logo
434 443 function generatePluginLogo(plugin) {
435 444 if (plugin.logo && plugin.logo.match(/^https?:\/\//)) {
436 - 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';">
437 446 <div class="default-plugin-icon" style="display:none;">📦</div>`;
438 447 } else {
439 - const slug = plugin.slug.includes('/') ? plugin.slug.split('/')[0] : plugin.slug;
440 - 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';">
441 - <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>`;
442 451 }
443 452 }
444 453
445 454 // Helper function to generate star rating
@@ -471,9 +480,9 @@
471 480
472 481 // Show error in plugin list
473 482 $pluginList.html(`
474 483 <div class="upk-error-state" style="text-align: center; padding: 40px;">
475 - <p style="color: #d63638;">${message}</p>
484 + <p style="color: #d63638;">${upkEsc(message)}</p>
476 485 <button type="button" class="bdt-button bdt-button-secondary" onclick="location.reload()">Retry</button>
477 486 </div>
478 487 `);
479 488 }