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 +213 -85 4.0.9 → 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';
@@ -201,9 +221,13 @@
201 221 if ( $categories && !is_wp_error( $categories ) ) {
202 222 foreach ( $categories as $category ) {
203 223 // Ensure $category is an object, not an array
204 224 if ( is_object( $category ) && isset( $category->term_id, $category->name, $category->slug ) ) {
205 - $link = '<a href="' . esc_url( get_category_link( $category->term_id ) ) . '">' . $category->name . '</a>';
225 + $link = '<a href="' . esc_url( get_category_link( $category->term_id ) ) . '"
226 + aria-label="' . esc_attr( 'Category ' . $category->name ) . '">'
227 + . esc_html( $category->name ) .
228 + '</a>';
229 +
206 230 $_categories[ $category->slug ] = $link;
207 231 }
208 232 }
209 233 }
@@ -331,8 +355,9 @@
331 355
332 356 /**
333 357 * HexColor
334 358 */
359 +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- established function name relied on across the plugin family / feedback SDK; renaming would break integration.
335 360 function strToHex( $string, $steps = -10 ) {
336 361
337 362 if ( empty( $string ) ) {
338 363 return false;
@@ -355,8 +380,52 @@
355 380
356 381 return strToUpper( $output );
357 382 }
358 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 +
359 428 function ultimate_post_kit_post_pagination( $wp_query, $widget_id = '' ) {
360 429
361 430 /** Stop execution if there's only 1 page */
362 431 if ( $wp_query->max_num_pages <= 1 ) {
@@ -362,23 +431,10 @@
362 431 if ( $wp_query->max_num_pages <= 1 ) {
363 432 return;
364 433 }
365 434
366 - // Get current page from multiple sources for reliability
367 - $paged_from_query = isset( $wp_query->query_vars['paged'] ) ? $wp_query->query_vars['paged'] : 0;
368 - $page_from_query = isset( $wp_query->query_vars['page'] ) ? $wp_query->query_vars['page'] : 0;
369 -
370 - if ( is_front_page() ) {
371 - // On front page, WordPress can use either 'page' or 'paged' depending on permalink structure
372 - $paged = max( get_query_var( 'page' ), get_query_var( 'paged' ), $paged_from_query, $page_from_query );
373 - $paged = $paged ? $paged : 1;
374 - $page_var = 'page';
375 - } else {
376 - $paged = max( get_query_var( 'paged' ), $paged_from_query );
377 - $paged = $paged ? $paged : 1;
378 - $page_var = 'paged';
379 - }
380 - $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;
381 437
382 438 /** Add current page to the array */
383 439 if ( $paged >= 1 ) {
384 440 $links[] = $paged;
@@ -394,9 +450,9 @@
394 450 $links[] = $paged + 2;
395 451 $links[] = $paged + 1;
396 452 }
397 453
398 - printf( '<ul class="upk-pagination" data-widget-id="%s" data-debug-paged="%s" data-debug-max="%s" data-debug-is-front="%s">' . "\n", esc_attr($widget_id), esc_attr($paged), esc_attr($max), esc_attr(is_front_page() ? 'yes' : 'no') );
454 + printf( '<ul class="upk-pagination" data-widget-id="%s">' . "\n", esc_attr($widget_id) );
399 455
400 456 /** Previous Post Link */
401 457 if ( $paged > 1 ) {
402 458 $prev_page = $paged - 1;
@@ -491,13 +547,15 @@
491 547 }
492 548
493 549 function ultimate_post_kit_post_time_diff( $format = '' ) {
494 550 $displayAgo = esc_html__( 'ago', 'ultimate-post-kit' );
551 + $post_time = get_the_time( 'U' );
552 + $current_time = current_time( 'timestamp' );
495 553
496 554 if ( $format == 'short' ) {
497 - $output = ultimate_post_kit_time_diff( strtotime( get_the_date() ), current_time( 'timestamp' ) );
555 + $output = ultimate_post_kit_time_diff( $post_time, $current_time );
498 556 } else {
499 - $output = human_time_diff( strtotime( get_the_date() ), current_time( 'timestamp' ) );
557 + $output = human_time_diff( $post_time, $current_time );
500 558 }
501 559
502 560 $output = $output . ' ' . $displayAgo;
503 561
@@ -761,11 +819,11 @@
761 819 'h3' => 'H3',
762 820 'h4' => 'H4',
763 821 'h5' => 'H5',
764 822 'h6' => 'H6',
765 - 'div' => 'div',
766 - 'span' => 'span',
767 - 'p' => 'p',
823 + 'div' => 'Div',
824 + 'span' => 'Span',
825 + 'p' => 'P',
768 826 ];
769 827
770 828 return $title_tags;
771 829 }
@@ -845,13 +903,24 @@
845 903
846 904 return wpautop( $output );
847 905 }
848 906
907 +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- established function name relied on across the plugin family / feedback SDK; renaming would break integration.
849 908 function get_user_role( $id ) {
909 + $user = new WP_User( $id );
910 + $role = array_shift( $user->roles );
850 911
851 - $user = new WP_User( $id );
912 + if ( empty( $role ) ) {
913 + return '';
914 + }
852 915
853 - 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'] );
854 923 }
855 924
856 925
857 926 /**
@@ -856,20 +925,61 @@
856 925
857 926 /**
858 927 * @param string $content return posts content
859 928 * @param int $avg_reading_speed average word reading speed per minute
929 + * @param string $hide_seconds whether to hide seconds (yes/no)
930 + * @param string $hide_minutes whether to hide minutes (yes/no)
860 931 *
861 932 * @return string return average reading time of specifiic posts.
862 933 */
863 934
864 935 if ( _is_upk_pro_activated() ) {
865 - function ultimate_post_kit_reading_time( $content, $avg_reading_speed ) {
866 - $total_word = str_word_count( strip_tags( $content ) );
936 + function ultimate_post_kit_reading_time( $content, $avg_reading_speed, $hide_seconds = 'no', $hide_minutes = 'no' ) {
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 ) );
867 941 $reading_minute = floor( $total_word / $avg_reading_speed );
868 942 $reading_seconds = floor( $total_word % $avg_reading_speed / ( $avg_reading_speed / 60 ) );
943 +
944 + $hide_seconds = ( $hide_seconds === 'yes' );
945 + $hide_minutes = ( $hide_minutes === 'yes' );
946 +
947 + // If hide_minutes is enabled, convert everything to seconds
948 + if ( $hide_minutes ) {
949 + $total_seconds = ( $reading_minute * 60 ) + $reading_seconds;
950 + if ( $hide_seconds ) {
951 + return '0 sec read';
952 + }
953 + return $total_seconds . ' sec read';
954 + }
955 +
869 956 if ( $total_word >= $avg_reading_speed ) {
870 - return $reading_minute . ' min ' . $reading_seconds . ' sec read';
957 + $parts = array();
958 +
959 + if ( $reading_minute > 0 ) {
960 + $parts[] = $reading_minute . ' min';
961 + }
962 +
963 + if ( ! $hide_seconds && $reading_seconds > 0 ) {
964 + $parts[] = $reading_seconds . ' sec';
965 + }
966 +
967 + // If no parts and seconds are not hidden, show at least seconds
968 + if ( empty( $parts ) ) {
969 + if ( ! $hide_seconds ) {
970 + $parts[] = $reading_seconds . ' sec';
971 + } else {
972 + $parts[] = $reading_minute . ' min';
973 + }
974 + }
975 +
976 + return ! empty( $parts ) ? implode( ' ', $parts ) . ' read' : '0 sec read';
871 977 } else {
978 + // For content less than reading speed, show seconds unless hidden
979 + if ( $hide_seconds ) {
980 + return '0 min read';
981 + }
872 982 return $reading_seconds . ' sec read';
873 983 }
874 984 }
875 985 }
@@ -878,8 +988,9 @@
878 988 /**
879 989 * License Validation
880 990 */
881 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.
882 993 function upk_license_validation() {
883 994
884 995 if ( function_exists( '_is_upk_pro_activated' ) && false === _is_upk_pro_activated() ) {
885 996 return false;
@@ -894,90 +1005,107 @@
894 1005 return false;
895 1006 }
896 1007 }
897 1008
1009 +
898 1010 /**
899 - * 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).
900 1023 */
901 -if ( ! function_exists( 'upk_inject_header_custom_code' ) ) {
902 - function upk_inject_header_custom_code() {
903 - if ( upk_is_page_excluded() ) {
904 - return;
905 - }
1024 +if ( ! function_exists( 'ultimate_post_kit_sanitize_public_post_type' ) ) {
1025 + function ultimate_post_kit_sanitize_public_post_type( $post_type, $fallback = 'post' ) {
906 1026
907 - $custom_css = get_option( 'upk_custom_css', '' );
908 - $custom_js = get_option( 'upk_custom_js', '' );
1027 + $is_allowed = static function ( $type ) {
1028 + $object = get_post_type_object( $type );
909 1029
910 - if ( ! empty( $custom_css ) ) {
911 - echo "\n<!-- Ultimate Post Kit Custom Header CSS -->\n";
912 - echo '<style type="text/css">' . "\n";
913 - echo $custom_css . "\n";
914 - 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;
915 1038 }
916 1039
917 - if ( ! empty( $custom_js ) ) {
918 - echo "\n<!-- Ultimate Post Kit Custom Header JS -->\n";
919 - echo '<script type="text/javascript">' . "\n";
920 - echo $custom_js . "\n";
921 - echo '</script>' . "\n";
1040 + if ( ! is_scalar( $post_type ) ) {
1041 + return $fallback;
922 1042 }
1043 +
1044 + $post_type = (string) $post_type;
1045 +
1046 + return $is_allowed( $post_type ) ? $post_type : $fallback;
923 1047 }
924 1048 }
925 1049
926 1050 /**
927 - * 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.
928 1060 */
929 -if ( ! function_exists( 'upk_inject_footer_custom_code' ) ) {
930 - function upk_inject_footer_custom_code() {
931 - if ( upk_is_page_excluded() ) {
932 - return;
933 - }
1061 +if ( ! function_exists( 'ultimate_post_kit_clamp_excerpt_length' ) ) {
1062 + function ultimate_post_kit_clamp_excerpt_length( $length, $default = 20 ) {
934 1063
935 - $custom_css_2 = get_option( 'upk_custom_css_2', '' );
936 - $custom_js_2 = get_option( 'upk_custom_js_2', '' );
1064 + $length = is_scalar( $length ) ? (int) $length : 0;
937 1065
938 - if ( ! empty( $custom_css_2 ) ) {
939 - echo "\n<!-- Ultimate Post Kit Custom Footer CSS -->\n";
940 - echo '<style type="text/css">' . "\n";
941 - echo $custom_css_2 . "\n";
942 - echo '</style>' . "\n";
1066 + if ( $length < 1 ) {
1067 + $length = (int) $default;
943 1068 }
944 1069
945 - if ( ! empty( $custom_js_2 ) ) {
946 - echo "\n<!-- Ultimate Post Kit Custom Footer JS -->\n";
947 - echo '<script type="text/javascript">' . "\n";
948 - echo $custom_js_2 . "\n";
949 - echo '</script>' . "\n";
950 - }
1070 + return max( 1, min( 200, $length ) );
951 1071 }
952 1072 }
953 1073
954 1074 /**
955 - * 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.
956 1086 */
957 -if ( ! function_exists( 'upk_is_page_excluded' ) ) {
958 - function upk_is_page_excluded() {
959 - $excluded_pages = get_option( 'upk_excluded_pages', array() );
960 -
961 - 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'] ) ) {
962 1091 return false;
963 1092 }
964 1093
965 - $current_id = 0;
966 -
967 - if ( is_home() && ! is_front_page() ) {
968 - $current_id = get_option( 'page_for_posts' );
969 - } elseif ( is_front_page() ) {
970 - $current_id = get_option( 'page_on_front' );
971 - } elseif ( is_singular() ) {
972 - $current_id = get_queried_object_id();
973 - } 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 ) {
974 1098 return false;
975 - } 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'] ) ) {
976 1103 return false;
977 - } elseif ( is_archive() ) {
978 - return false;
979 1104 }
980 1105
981 - return in_array( $current_id, $excluded_pages );
1106 + return [
1107 + 'library' => $library,
1108 + 'value' => (string) $icon['value'],
1109 + ];
982 1110 }
983 1111 }