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