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 | traits/global-widget-functions.php +76 -5 4.5.0 → 4.5.6 View file →
@@ -8,8 +8,44 @@
8 8
9 9 trait Global_Widget_Functions {
10 10
11 11 /**
12 + * Default display flags for the load-more AJAX handlers.
13 + *
14 + * The widgets always emit every one of these keys into their data-settings
15 + * payload, so this never changes rendering for a real request. It only matters
16 + * for the unauthenticated wp_ajax_nopriv_* endpoints, where a partial payload
17 + * would otherwise make the render loop read undefined array keys and emit a
18 + * PHP warning per missing key. Values mirror the widgets' own defaults.
19 + *
20 + * @return array<string, string>
21 + */
22 + protected function loadmore_display_defaults() {
23 + return [
24 + 'show_title' => 'yes',
25 + 'title_tags' => 'h3',
26 + 'title_style' => 'underline',
27 + 'show_author' => 'yes',
28 + 'show_author_name' => 'yes',
29 + 'show_author_avatar' => 'yes',
30 + 'show_date' => 'yes',
31 + 'show_time' => 'no',
32 + 'show_category' => 'yes',
33 + 'show_excerpt' => 'yes',
34 + 'show_readmore' => 'yes',
35 + 'readmore_type' => '',
36 + 'show_comments' => 'no',
37 + 'show_image' => 'yes',
38 + 'show_post_format' => 'no',
39 + 'show_reading_time' => 'no',
40 + 'show_counter_number' => 'no',
41 + 'human_diff_time' => 'no',
42 + 'upk_link_new_tab' => 'no',
43 + 'meta_separator' => '//',
44 + ];
45 + }
46 +
47 + /**
12 48 * Render Ajax Qery Posts
13 49 */
14 50 function mapGroupControlQuery($term_ids = []) {
15 51 $terms = get_terms(
@@ -31,11 +67,26 @@
31 67 function query_args() {
32 68 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified in the load-more AJAX handler (check_ajax_referer 'upk-site') before this runs.
33 69 if ( isset( $_POST['settings'] ) && is_array( $_POST['settings'] ) ) {
34 70 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified in the load-more AJAX handler (check_ajax_referer 'upk-site') before this runs.
35 - extract( map_deep( wp_unslash( $_POST['settings'] ), 'sanitize_text_field' ) );
71 + $request_settings = map_deep( wp_unslash( $_POST['settings'] ), 'sanitize_text_field' );
72 +
73 + unset( $request_settings['this'], $request_settings['GLOBALS'] );
74 +
75 + extract( $request_settings, EXTR_SKIP );
36 76 }
37 77
78 + // extract() only defines what the request actually sent, and this handler is
79 + // reachable unauthenticated, so any omitted key would leave the matching
80 + // variable undefined. The three below are consumed unconditionally further
81 + // down (orderby/order in $args, and $posts_select_date in the date query),
82 + // unlike their siblings which are isset()-guarded at the point of use.
83 + // Seed them so a partial request cannot emit PHP warnings or push a null
84 + // into WP_Query.
85 + $posts_orderby = isset( $posts_orderby ) ? $posts_orderby : 'date';
86 + $posts_order = isset( $posts_order ) ? $posts_order : 'DESC';
87 + $posts_select_date = isset( $posts_select_date ) ? $posts_select_date : '';
88 +
38 89 // This handler is reachable unauthenticated (wp_ajax_nopriv_*). Clamp the
39 90 // page size to a sane positive maximum so a request cannot ask for -1
40 91 // ("all posts") or a huge value and turn load-more into a DoS amplifier.
41 92 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified in the load-more AJAX handler (check_ajax_referer 'upk-site') before this runs.
@@ -121,8 +172,10 @@
121 172 }
122 173
123 174 $exclude_by = isset($posts_exclude_by) ? $posts_exclude_by : [];
124 175 $include_by = isset($posts_include_by) ? $posts_include_by : [];
176 + $exclude_by = is_array($exclude_by) ? $exclude_by : ( '' === $exclude_by || null === $exclude_by ? [] : [ $exclude_by ] );
177 + $include_by = is_array($include_by) ? $include_by : ( '' === $include_by || null === $include_by ? [] : [ $include_by ] );
125 178 $include_users = [];
126 179 $exclude_users = [];
127 180 // print_r($exclude_by);
128 181 /**
@@ -145,9 +198,19 @@
145 198 * Set Including Manually
146 199 */
147 200 $selected_ids = $posts_selected_ids;
148 201 $selected_ids = wp_parse_id_list($selected_ids);
149 - $args['post_type'] = 'any';
202 +
203 + // Both $posts_source and $posts_selected_ids arrive from $_POST on the
204 + // wp_ajax_nopriv_* load-more handlers, so an anonymous caller can pick this
205 + // branch and name arbitrary IDs. 'any' only filters on exclude_from_search,
206 + // which is weaker than the allowlist the else branch below applies: a post
207 + // type registered public => true, publicly_queryable => false is rejected by
208 + // is_post_type_viewable() but still matched by 'any'. Route this branch
209 + // through the same allowlist so every path out of query_args() agrees.
210 + $args['post_type'] = ultimate_post_kit_sanitize_public_post_type(
211 + array_values( get_post_types( [ 'public' => true, 'exclude_from_search' => false ] ) )
212 + );
150 213 if (!empty($selected_ids)) {
151 214 $args['post__in'] = $selected_ids;
152 215 }
153 216 $args['ignore_sticky_posts'] = 1;
@@ -165,10 +228,10 @@
165 228 $related_post_id = is_singular() && (0 !== $post_id) ? $post_id : null;
166 229 $args['post_type'] = get_post_type($related_post_id);
167 230
168 231 // $include_by = $this->getGroupControlQueryParamBy('include');
169 - if (in_array('authors', $include_by)) {
170 - $args['author__in'] = wp_parse_id_list($settings['posts_include_author_ids']);
232 + if (in_array('authors', $include_by) && isset($posts_include_author_ids)) {
233 + $args['author__in'] = wp_parse_id_list($posts_include_author_ids);
171 234 } else {
172 235 $args['author__in'] = get_post_field('post_author', $related_post_id);
173 236 }
174 237
@@ -184,9 +247,13 @@
184 247
185 248 $args['ignore_sticky_posts'] = 1;
186 249 $args = apply_filters('ultimate_post_kit/query/get_query_args/related_query', $args);
187 250 } else {
188 - $args['post_type'] = $posts_source;
251 + // This runs on wp_ajax_nopriv_* and $posts_source comes straight from $_POST,
252 + // so restrict it to post types this site already exposes to anonymous visitors.
253 + // Without this a request can enumerate published entries of post types that are
254 + // deliberately hidden from anonymous access (e.g. Elementor's elementor_library).
255 + $args['post_type'] = ultimate_post_kit_sanitize_public_post_type( $posts_source );
189 256 $current_post = [];
190 257
191 258 /**
192 259 * Set Taxonomy && Set Authors
@@ -262,8 +329,12 @@
262 329 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- Elementor widget query built from user-configured controls; expected behaviour.
263 330 $args['tax_query'] = $terms_query;
264 331 $args['tax_query']['relation'] = 'AND';
265 332 }
333 + }
334 +
335 + if ( empty( $args['post_status'] ) || 'publish' !== $args['post_status'] ) {
336 + $args['post_status'] = 'publish';
266 337 }
267 338
268 339 $ajaxposts = new \WP_Query($args);
269 340 return $ajaxposts;