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/class-remote-data-handler.php +95 -7 4.5.0 → 4.5.6 View file →
@@ -26,8 +26,61 @@
26 26 */
27 27 const CACHE_KEY = 'bdt_remote_plugins_data';
28 28
29 29 /**
30 + * Bundled logo file name for each plugin slug.
31 + *
32 + * These ship with the plugin (assets/images/others-plugin-logo/) so the plugin
33 + * cards render our own branded artwork instead of the wordpress.org icon, and
34 + * still show something for plugins whose .org listing has no icon at all.
35 + * The key is the wordpress.org slug; the value is the file base name.
36 + *
37 + * @var array<string, string>
38 + */
39 + const LOCAL_PLUGIN_LOGOS = [
40 + 'bdthemes-element-pack-lite' => 'element-pack',
41 + 'bdthemes-prime-slider-lite' => 'prime-slider',
42 + 'ultimate-post-kit' => 'ultimate-post-kit',
43 + 'ultimate-store-kit' => 'ultimate-store-kit',
44 + 'zoloblocks' => 'zoloblocks',
45 + 'pixel-gallery' => 'pixel-gallery',
46 + 'live-copy-paste' => 'live-copy-paste',
47 + 'spin-wheel' => 'spin-wheel',
48 + 'ai-image' => 'ai-image',
49 + 'dark-reader' => 'dark-reader',
50 + 'ar-viewer' => 'ar-viewer',
51 + 'smart-admin-assistant' => 'smart-admin-assistant',
52 + 'website-accessibility' => 'one-accessibility',
53 + 'launch-guard' => 'launch-guard',
54 + 'sigma-forms' => 'sigma-forms',
55 + 'sigma-media-manager' => 'sigma-media-manager',
56 + 'sigma-store-locator' => 'sigma-store-locator',
57 + 'swift-checkout' => 'swift-checkout',
58 + ];
59 +
60 + /**
61 + * Resolve the bundled logo URL for a plugin slug.
62 + *
63 + * @param string $slug Plugin slug.
64 + * @return string Logo URL, or an empty string when the slug has no bundled logo.
65 + */
66 + public static function get_local_plugin_logo( $slug ) {
67 + if ( ! is_string( $slug ) || '' === $slug || ! isset( self::LOCAL_PLUGIN_LOGOS[ $slug ] ) ) {
68 + return '';
69 + }
70 +
71 + $file = self::LOCAL_PLUGIN_LOGOS[ $slug ] . '.png';
72 +
73 + // Only advertise the file if it actually shipped, so a trimmed build falls
74 + // back to the remote icon rather than rendering a broken image.
75 + if ( defined( 'BDTUPK_PATH' ) && ! file_exists( BDTUPK_PATH . 'assets/images/others-plugin-logo/' . $file ) ) {
76 + return '';
77 + }
78 +
79 + return BDTUPK_ASSETS_URL . 'images/others-plugin-logo/' . $file;
80 + }
81 +
82 + /**
30 83 * Cron hook name for background fetch
31 84 */
32 85 const CRON_HOOK = 'bdt_fetch_remote_plugins_cron';
33 86
@@ -153,9 +206,9 @@
153 206 */
154 207 public static function ajax_get_plugins() {
155 208 // Verify nonce for security
156 209 if (!check_ajax_referer('upk_get_plugins_nonce', 'nonce', false)) {
157 - wp_die(esc_html__('Security check failed.', 'ultimate-post-kit'));
210 + wp_send_json_error(['message' => __('Security check failed.', 'ultimate-post-kit')], 403);
158 211 }
159 212
160 213 // Gate to users who could act on it; also prevents the synchronous
161 214 // remote-fetch trigger below from being reachable without capability.
@@ -209,11 +262,11 @@
209 262 $last_updated_formatted = self::format_last_updated($data['last_updated']);
210 263 }
211 264
212 265 $formatted_plugins[] = [
213 - 'name' => $data['name'] ?? '',
266 + 'name' => self::decode_api_text($data['name'] ?? ''),
214 267 'slug' => $data['slug'] ?? '',
215 - 'description' => $data['description'] ?? '',
268 + 'description' => self::decode_api_text($data['description'] ?? ''),
216 269 'logo' => $data['logo'] ?? '',
217 270 'rating' => $data['rating'] ?? 0,
218 271 'rating_percentage' => $data['rating_percentage'] ?? 0,
219 272 'num_ratings' => $data['num_ratings'] ?? 0,
@@ -240,8 +293,32 @@
240 293 ]);
241 294 }
242 295
243 296 /**
297 + * Decode display text coming from the WordPress.org plugins API.
298 + *
299 + * The API returns strings that are already HTML-encoded, e.g.
300 + * "Element Pack Lite &#8211; Addons for Elementor". The renderer escapes
301 + * again before injecting into the DOM, which turns the leading "&" into
302 + * "&amp;" and prints the entity literally instead of an en dash. Decoding
303 + * here means exactly one round of escaping happens, at output.
304 + *
305 + * Applied when building the response rather than when caching, so
306 + * already-cached entries are corrected without waiting for the transient
307 + * to expire.
308 + *
309 + * @param mixed $text Raw value from the API.
310 + * @return string Plain text, still to be escaped at output.
311 + */
312 + private static function decode_api_text($text) {
313 + if (!is_string($text) || '' === $text) {
314 + return '';
315 + }
316 +
317 + return html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
318 + }
319 +
320 + /**
244 321 * Schedule the cron job on init
245 322 */
246 323 public static function schedule_cron() {
247 324 // Make sure the cron hook is registered
@@ -414,11 +491,16 @@
414 491 * @param array $raw_data Raw API data
415 492 * @return array Formatted plugin data
416 493 */
417 494 private static function format_plugin_data($raw_data) {
418 - // Get the best available icon with validation
419 - $icon_url = self::get_valid_plugin_icon($raw_data['icons'] ?? []);
495 + // Prefer the logo bundled with this plugin so the cards show our own branded
496 + // artwork; fall back to the wordpress.org icon for anything not bundled.
497 + $icon_url = self::get_local_plugin_logo($raw_data['slug'] ?? '');
420 498
499 + if ('' === $icon_url) {
500 + $icon_url = self::get_valid_plugin_icon($raw_data['icons'] ?? []);
501 + }
502 +
421 503 // Format active installs with null safety and real data
422 504 $active_installs_raw = $raw_data['active_installs'] ?? 0;
423 505 $active_installs = self::format_active_installs($active_installs_raw);
424 506 $active_installs_count = self::get_numeric_active_installs($active_installs_raw);
@@ -464,10 +546,16 @@
464 546 * @return string Valid icon URL or empty string
465 547 */
466 548 private static function get_valid_plugin_icon($icons) {
467 549 $valid_extensions = ['gif', 'png', 'jpg', 'jpeg', 'svg'];
468 - $icon_sizes = ['256', '128', 'default'];
469 -
550 +
551 + // The wordpress.org plugin_information API returns its icon map keyed by
552 + // '2x' / '1x' (and 'svg' or 'default' for the generated geopattern icon) --
553 + // never '256' / '128'. Looking only for the pixel keys meant no plugin icon
554 + // ever resolved and every card fell back to the placeholder. Highest quality
555 + // first, with the old pixel keys kept for any cached/legacy payload.
556 + $icon_sizes = ['2x', '1x', 'svg', 'default', '256', '128'];
557 +
470 558 foreach ($icon_sizes as $size) {
471 559 if (!empty($icons[$size])) {
472 560 $icon_url = $icons[$size];
473 561