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/helper.php +163 -106 4.1.2 → 4.5.6 View file →
@@ -2,8 +2,12 @@
2 2
3 3 use UltimatePostKit\Ultimate_Post_Kit_Loader;
4 4 use Elementor\Plugin;
5 5
6 +if (!defined('ABSPATH')) {
7 + exit; // Exit if accessed directly.
8 +}
9 +
6 10 /**
7 11 * You can easily add white label branding for for extended license or multi site license.
8 12 * Don't try for regular license otherwise your license will be invalid.
9 13 * return white label
@@ -165,10 +169,25 @@
165 169 $tax_output = 'objects'; // or objects
166 170 $taxonomies = get_taxonomies( $args, $tax_output );
167 171 if ( $taxonomies ) {
168 172 foreach ( $taxonomies as $taxonomy ) {
169 - $post_type_obj = get_post_type_object( $taxonomy->object_type[0] );
170 - $output[ $taxonomy->name ] = ( $taxonomy->label ? $taxonomy->label : '' ) . ' (' . isset( $post_type_obj->label ) . ')';
173 + $taxonomy_label = $taxonomy->label ? $taxonomy->label : $taxonomy->name;
174 + $term_count = wp_count_terms(
175 + [
176 + 'taxonomy' => $taxonomy->name,
177 + 'hide_empty' => false,
178 + ]
179 + );
180 +
181 + if ( is_wp_error( $term_count ) ) {
182 + $term_count = 0;
183 + }
184 +
185 + $output[ $taxonomy->name ] = sprintf(
186 + '%s (%d)',
187 + $taxonomy_label,
188 + (int) $term_count
189 + );
171 190 }
172 191 }
173 192
174 193 return $output;
@@ -173,8 +192,9 @@
173 192
174 193 return $output;
175 194 }
176 195
196 +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- established function name relied on across the plugin family / feedback SDK; renaming would break integration.
177 197 function upk_get_category( $post_type ) {
178 198 switch ( $post_type ) {
179 199 case 'campaign':
180 200 $taxonomy = 'campaign_category';
@@ -335,8 +355,9 @@
335 355
336 356 /**
337 357 * HexColor
338 358 */
359 +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- established function name relied on across the plugin family / feedback SDK; renaming would break integration.
339 360 function strToHex( $string, $steps = -10 ) {
340 361
341 362 if ( empty( $string ) ) {
342 363 return false;
@@ -359,8 +380,52 @@
359 380
360 381 return strToUpper( $output );
361 382 }
362 383
384 +/**
385 + * Get the current paged number for a custom WP_Query instance.
386 + *
387 + * @param \WP_Query $wp_query Query object.
388 + * @return int Current page number.
389 + */
390 +function ultimate_post_kit_get_query_paged( $wp_query ) {
391 + if ( ! $wp_query instanceof \WP_Query ) {
392 + return 1;
393 + }
394 +
395 + $paged_from_query = isset( $wp_query->query_vars['paged'] ) ? (int) $wp_query->query_vars['paged'] : 0;
396 + $page_from_query = isset( $wp_query->query_vars['page'] ) ? (int) $wp_query->query_vars['page'] : 0;
397 +
398 + if ( is_front_page() ) {
399 + $paged = max( get_query_var( 'page' ), get_query_var( 'paged' ), $paged_from_query, $page_from_query );
400 + } else {
401 + $paged = max( get_query_var( 'paged' ), $paged_from_query );
402 + }
403 +
404 + return max( 1, (int) $paged );
405 +}
406 +
407 +/**
408 + * Get item counter offset for paginated query loops.
409 + *
410 + * @param \WP_Query $wp_query Query object.
411 + * @return int Zero-based offset for the first item on the current page.
412 + */
413 +function ultimate_post_kit_get_query_counter_offset( $wp_query ) {
414 + if ( ! $wp_query instanceof \WP_Query ) {
415 + return 0;
416 + }
417 +
418 + $paged = ultimate_post_kit_get_query_paged( $wp_query );
419 + $posts_per_page = (int) $wp_query->get( 'posts_per_page' );
420 +
421 + if ( 1 >= $paged || 0 >= $posts_per_page ) {
422 + return 0;
423 + }
424 +
425 + return ( $paged - 1 ) * $posts_per_page;
426 +}
427 +
363 428 function ultimate_post_kit_post_pagination( $wp_query, $widget_id = '' ) {
364 429
365 430 /** Stop execution if there's only 1 page */
366 431 if ( $wp_query->max_num_pages <= 1 ) {
@@ -366,24 +431,10 @@
366 431 if ( $wp_query->max_num_pages <= 1 ) {
367 432 return;
368 433 }
369 434
370 - // Get current page from multiple sources for reliability
371 - $paged_from_query = isset( $wp_query->query_vars['paged'] ) ? $wp_query->query_vars['paged'] : 0;
372 - $page_from_query = isset( $wp_query->query_vars['page'] ) ? $wp_query->query_vars['page'] : 0;
373 -
374 - if ( is_front_page() ) {
375 - // On front page, WordPress can use either 'page' or 'paged' depending on permalink structure
376 - $paged = max( get_query_var( 'page' ), get_query_var( 'paged' ), $paged_from_query, $page_from_query );
377 - $paged = $paged ? $paged : 1;
378 - $page_var = 'page';
379 - } else {
380 - $paged = max( get_query_var( 'paged' ), $paged_from_query );
381 - $paged = $paged ? $paged : 1;
382 - $page_var = 'paged';
383 - }
384 -
385 - $max = intval( $wp_query->max_num_pages );
435 + $paged = ultimate_post_kit_get_query_paged( $wp_query );
436 + $max = (int) $wp_query->max_num_pages;
386 437
387 438 /** Add current page to the array */
388 439 if ( $paged >= 1 ) {
389 440 $links[] = $paged;
@@ -510,34 +561,8 @@
510 561
511 562 return $output;
512 563 }
513 564
514 -// Filter to override WordPress posts_per_page for Builder pages
515 -add_action('pre_get_posts', 'ultimate_post_kit_override_posts_per_page_for_builder');
516 -
517 -function ultimate_post_kit_override_posts_per_page_for_builder($query) {
518 - // Only affect main query
519 - if (!$query->is_main_query()) {
520 - return;
521 - }
522 -
523 - // Check if we have pagination in URL
524 - $paged = max(1, get_query_var('paged'), get_query_var('page'));
525 -
526 - // Only apply override on paginated pages (page > 1)
527 - if ($paged <= 1) {
528 - return;
529 - }
530 -
531 - $post_id = get_queried_object_id();
532 -
533 - if ($post_id && function_exists('get_post_meta')) {
534 - // Set posts_per_page to -1 to show all posts and avoid pagination conflicts
535 - $query->set('posts_per_page', -1);
536 - $query->set('nopaging', true);
537 - }
538 -}
539 -
540 565 function ultimate_post_kit_iso_time( $time ) {
541 566 $current_offset = (float) get_option( 'gmt_offset' );
542 567 $timezone_string = get_option( 'timezone_string' );
543 568
@@ -794,11 +819,11 @@
794 819 'h3' => 'H3',
795 820 'h4' => 'H4',
796 821 'h5' => 'H5',
797 822 'h6' => 'H6',
798 - 'div' => 'div',
799 - 'span' => 'span',
800 - 'p' => 'p',
823 + 'div' => 'Div',
824 + 'span' => 'Span',
825 + 'p' => 'P',
801 826 ];
802 827
803 828 return $title_tags;
804 829 }
@@ -878,13 +903,24 @@
878 903
879 904 return wpautop( $output );
880 905 }
881 906
907 +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- established function name relied on across the plugin family / feedback SDK; renaming would break integration.
882 908 function get_user_role( $id ) {
909 + $user = new WP_User( $id );
910 + $role = array_shift( $user->roles );
883 911
884 - $user = new WP_User( $id );
912 + if ( empty( $role ) ) {
913 + return '';
914 + }
885 915
886 - return array_shift( $user->roles );
916 + $wp_roles = wp_roles();
917 +
918 + if ( ! isset( $wp_roles->roles[ $role ]['name'] ) ) {
919 + return '';
920 + }
921 +
922 + return translate_user_role( $wp_roles->roles[ $role ]['name'] );
887 923 }
888 924
889 925
890 926 /**
@@ -897,9 +933,12 @@
897 933 */
898 934
899 935 if ( _is_upk_pro_activated() ) {
900 936 function ultimate_post_kit_reading_time( $content, $avg_reading_speed, $hide_seconds = 'no', $hide_minutes = 'no' ) {
901 - $total_word = str_word_count( strip_tags( $content ) );
937 + $avg_reading_speed = is_scalar( $avg_reading_speed ) ? (int) $avg_reading_speed : 0;
938 + $avg_reading_speed = $avg_reading_speed > 0 ? $avg_reading_speed : 200;
939 +
940 + $total_word = str_word_count( wp_strip_all_tags( $content ) );
902 941 $reading_minute = floor( $total_word / $avg_reading_speed );
903 942 $reading_seconds = floor( $total_word % $avg_reading_speed / ( $avg_reading_speed / 60 ) );
904 943
905 944 $hide_seconds = ( $hide_seconds === 'yes' );
@@ -949,8 +988,9 @@
949 988 /**
950 989 * License Validation
951 990 */
952 991 if ( ! function_exists( 'upk_license_validation' ) ) {
992 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- established function name relied on across the plugin family / feedback SDK; renaming would break integration.
953 993 function upk_license_validation() {
954 994
955 995 if ( function_exists( '_is_upk_pro_activated' ) && false === _is_upk_pro_activated() ) {
956 996 return false;
@@ -965,90 +1005,107 @@
965 1005 return false;
966 1006 }
967 1007 }
968 1008
1009 +
969 1010 /**
970 - * Inject custom CSS and JS into the header
1011 + * Restrict a request-supplied post type to the ones this site already exposes to
1012 + * anonymous visitors.
1013 + *
1014 + * The load-more handlers are registered on wp_ajax_nopriv_* and rebuild their WP_Query
1015 + * from $_POST, so the post type they query is attacker-controlled. Post types that are
1016 + * public but flagged exclude_from_search (Elementor's elementor_library, for example)
1017 + * are deliberately hidden from anonymous visitors elsewhere, so they must not be
1018 + * reachable here either.
1019 + *
1020 + * @param string|array $post_type Requested post type(s).
1021 + * @param string $fallback Post type to fall back to when nothing is allowed.
1022 + * @return string|array Sanitized post type(s).
971 1023 */
972 -if ( ! function_exists( 'upk_inject_header_custom_code' ) ) {
973 - function upk_inject_header_custom_code() {
974 - if ( upk_is_page_excluded() ) {
975 - return;
976 - }
1024 +if ( ! function_exists( 'ultimate_post_kit_sanitize_public_post_type' ) ) {
1025 + function ultimate_post_kit_sanitize_public_post_type( $post_type, $fallback = 'post' ) {
977 1026
978 - $custom_css = get_option( 'upk_custom_css', '' );
979 - $custom_js = get_option( 'upk_custom_js', '' );
1027 + $is_allowed = static function ( $type ) {
1028 + $object = get_post_type_object( $type );
980 1029
981 - if ( ! empty( $custom_css ) ) {
982 - echo "\n<!-- Ultimate Post Kit Custom Header CSS -->\n";
983 - echo '<style type="text/css">' . "\n";
984 - echo $custom_css . "\n";
985 - echo '</style>' . "\n";
1030 + return $object && is_post_type_viewable( $type ) && empty( $object->exclude_from_search );
1031 + };
1032 +
1033 + if ( is_array( $post_type ) ) {
1034 + $requested = array_filter( $post_type, 'is_scalar' );
1035 + $requested = array_values( array_filter( array_map( 'strval', $requested ), $is_allowed ) );
1036 +
1037 + return empty( $requested ) ? $fallback : $requested;
986 1038 }
987 1039
988 - if ( ! empty( $custom_js ) ) {
989 - echo "\n<!-- Ultimate Post Kit Custom Header JS -->\n";
990 - echo '<script type="text/javascript">' . "\n";
991 - echo $custom_js . "\n";
992 - echo '</script>' . "\n";
1040 + if ( ! is_scalar( $post_type ) ) {
1041 + return $fallback;
993 1042 }
1043 +
1044 + $post_type = (string) $post_type;
1045 +
1046 + return $is_allowed( $post_type ) ? $post_type : $fallback;
994 1047 }
995 1048 }
996 1049
997 1050 /**
998 - * Inject custom CSS and JS into the footer
1051 + * Clamp a request-supplied excerpt word count.
1052 + *
1053 + * excerpt_length arrives from $_POST on the unauthenticated load-more handlers and is
1054 + * passed straight to wp_trim_words(), so an unbounded value returns effectively the
1055 + * whole post_content instead of a teaser.
1056 + *
1057 + * @param mixed $length Requested word count.
1058 + * @param int $default Value to use when the request supplies nothing usable.
1059 + * @return int Clamped word count.
999 1060 */
1000 -if ( ! function_exists( 'upk_inject_footer_custom_code' ) ) {
1001 - function upk_inject_footer_custom_code() {
1002 - if ( upk_is_page_excluded() ) {
1003 - return;
1004 - }
1061 +if ( ! function_exists( 'ultimate_post_kit_clamp_excerpt_length' ) ) {
1062 + function ultimate_post_kit_clamp_excerpt_length( $length, $default = 20 ) {
1005 1063
1006 - $custom_css_2 = get_option( 'upk_custom_css_2', '' );
1007 - $custom_js_2 = get_option( 'upk_custom_js_2', '' );
1064 + $length = is_scalar( $length ) ? (int) $length : 0;
1008 1065
1009 - if ( ! empty( $custom_css_2 ) ) {
1010 - echo "\n<!-- Ultimate Post Kit Custom Footer CSS -->\n";
1011 - echo '<style type="text/css">' . "\n";
1012 - echo $custom_css_2 . "\n";
1013 - echo '</style>' . "\n";
1066 + if ( $length < 1 ) {
1067 + $length = (int) $default;
1014 1068 }
1015 1069
1016 - if ( ! empty( $custom_js_2 ) ) {
1017 - echo "\n<!-- Ultimate Post Kit Custom Footer JS -->\n";
1018 - echo '<script type="text/javascript">' . "\n";
1019 - echo $custom_js_2 . "\n";
1020 - echo '</script>' . "\n";
1021 - }
1070 + return max( 1, min( 200, $length ) );
1022 1071 }
1023 1072 }
1024 1073
1025 1074 /**
1026 - * Check if current page should be excluded from custom code injection
1075 + * Make a request-supplied Elementor icon array safe to render.
1076 + *
1077 + * The load-more handlers run on wp_ajax_nopriv_* and rebuild their settings from $_POST.
1078 + * map_deep() preserves nested arrays, so an icon array reaches
1079 + * Elementor\Icons_Manager::render_icon() exactly as the caller shaped it. The 'svg'
1080 + * library branch resolves to Svg::get_inline_svg( $value['id'] ), which reads an
1081 + * attachment by id with no capability or post-status check, so an icon coming from a
1082 + * request must never be allowed to select it.
1083 + *
1084 + * @param mixed $icon Icon array as supplied by the request.
1085 + * @return array|false Safe icon array, or false when nothing renderable remains.
1027 1086 */
1028 -if ( ! function_exists( 'upk_is_page_excluded' ) ) {
1029 - function upk_is_page_excluded() {
1030 - $excluded_pages = get_option( 'upk_excluded_pages', array() );
1031 -
1032 - if ( empty( $excluded_pages ) || ! is_array( $excluded_pages ) ) {
1087 +if ( ! function_exists( 'ultimate_post_kit_sanitize_request_icon' ) ) {
1088 + function ultimate_post_kit_sanitize_request_icon( $icon ) {
1089 +
1090 + if ( ! is_array( $icon ) || empty( $icon['library'] ) || ! is_scalar( $icon['library'] ) ) {
1033 1091 return false;
1034 1092 }
1035 1093
1036 - $current_id = 0;
1037 -
1038 - if ( is_home() && ! is_front_page() ) {
1039 - $current_id = get_option( 'page_for_posts' );
1040 - } elseif ( is_front_page() ) {
1041 - $current_id = get_option( 'page_on_front' );
1042 - } elseif ( is_singular() ) {
1043 - $current_id = get_queried_object_id();
1044 - } elseif ( is_category() || is_tag() || is_tax() ) {
1094 + $library = (string) $icon['library'];
1095 +
1096 + // Uploaded-SVG icons are addressed by attachment id; never resolve one from a request.
1097 + if ( 'svg' === $library ) {
1045 1098 return false;
1046 - } elseif ( is_author() ) {
1099 + }
1100 +
1101 + // Font icons are rendered as a CSS class, so the value must stay a scalar.
1102 + if ( ! isset( $icon['value'] ) || ! is_scalar( $icon['value'] ) ) {
1047 1103 return false;
1048 - } elseif ( is_archive() ) {
1049 - return false;
1050 1104 }
1051 1105
1052 - return in_array( $current_id, $excluded_pages );
1106 + return [
1107 + 'library' => $library,
1108 + 'value' => (string) $icon['value'],
1109 + ];
1053 1110 }
1054 1111 }