← All changes
|
includes/controls/select-input/dynamic-select-input-module.php
+52
-11
4.1.13
→
4.5.6
View file →
| @@ -38,9 +38,9 @@ | ||
| 38 | 38 | /** |
| 39 | 39 | * get Ajax Data |
| 40 | 40 | */ |
| 41 | 41 | public function getSelectInputData() { |
| 42 | - $nonce = isset($_POST['security']) ? sanitize_text_field($_POST['security']) : ''; | |
| 42 | + $nonce = isset($_POST['security']) ? sanitize_text_field(wp_unslash($_POST['security'])) : ''; | |
| 43 | 43 | |
| 44 | 44 | try { |
| 45 | 45 | if (!wp_verify_nonce($nonce, 'upk_dynamic_select')) { |
| 46 | 46 | throw new \Exception('Invalid request'); |
| @@ -49,9 +49,9 @@ | ||
| 49 | 49 | if (!current_user_can('edit_posts')) { |
| 50 | 50 | throw new \Exception('Unauthorized request'); |
| 51 | 51 | } |
| 52 | 52 | |
| 53 | - $query = isset($_POST['query']) ? sanitize_text_field($_POST['query']) : ''; | |
| 53 | + $query = isset($_POST['query']) ? sanitize_text_field(wp_unslash($_POST['query'])) : ''; | |
| 54 | 54 | |
| 55 | 55 | if ($query == 'terms') { |
| 56 | 56 | $data = $this->getTerms(); |
| 57 | 57 | } else if ($query == 'authors') { |
| @@ -72,9 +72,10 @@ | ||
| 72 | 72 | * Get Post Type |
| 73 | 73 | * @return string |
| 74 | 74 | */ |
| 75 | 75 | protected function getPostType() { |
| 76 | - return isset($_POST['post_type']) ? sanitize_text_field($_POST['post_type']) : ''; | |
| 76 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified in getSelectInputData() before this helper runs. | |
| 77 | + return isset($_POST['post_type']) ? sanitize_text_field(wp_unslash($_POST['post_type'])) : ''; | |
| 77 | 78 | } |
| 78 | 79 | |
| 79 | 80 | /** |
| 80 | 81 | * @return string[]|\WP_Post_Type[] |
| @@ -86,9 +87,10 @@ | ||
| 86 | 87 | /** |
| 87 | 88 | * @return string |
| 88 | 89 | */ |
| 89 | 90 | protected function getSearchQuery() { |
| 90 | - return isset($_POST['search_text']) ? sanitize_text_field($_POST['search_text']) : ''; | |
| 91 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified in getSelectInputData() before this helper runs. | |
| 92 | + return isset($_POST['search_text']) ? sanitize_text_field(wp_unslash($_POST['search_text'])) : ''; | |
| 91 | 93 | } |
| 92 | 94 | |
| 93 | 95 | /** |
| 94 | 96 | * @return array|mixed |
| @@ -93,9 +95,17 @@ | ||
| 93 | 95 | /** |
| 94 | 96 | * @return array|mixed |
| 95 | 97 | */ |
| 96 | 98 | protected function getselecedIds() { |
| 97 | - return isset($_POST['ids']) ? sanitize_text_field($_POST['ids']) : []; | |
| 99 | + if ( ! check_ajax_referer( 'upk_dynamic_select', 'security', false ) ) { | |
| 100 | + return []; | |
| 101 | + } | |
| 102 | + | |
| 103 | + if ( ! isset( $_POST['ids'] ) ) { | |
| 104 | + return []; | |
| 105 | + } | |
| 106 | + | |
| 107 | + return array_values( array_filter( wp_parse_id_list( (array) wp_unslash( $_POST['ids'] ) ) ) ); | |
| 98 | 108 | } |
| 99 | 109 | |
| 100 | 110 | |
| 101 | 111 | /** |
| @@ -129,18 +139,26 @@ | ||
| 129 | 139 | $args = []; |
| 130 | 140 | |
| 131 | 141 | $args['post_status'] = 'publish'; |
| 132 | 142 | |
| 133 | - if ($this->getPostType()) { | |
| 134 | - $args['post_type'] = $this->getPostType(); | |
| 143 | + $public_post_types = $this->getAllPublicPostTypes(); | |
| 144 | + $requested_post_type = $this->getPostType(); | |
| 145 | + | |
| 146 | + // post_type comes straight from $_POST. Restrict it to the public post types this | |
| 147 | + // control is meant to browse so it cannot be pointed at a private post type. | |
| 148 | + if ($requested_post_type && in_array($requested_post_type, $public_post_types, true)) { | |
| 149 | + $args['post_type'] = $requested_post_type; | |
| 135 | 150 | } else { |
| 136 | - $args['post_type'] = $this->getAllPublicPostTypes(); | |
| 151 | + $args['post_type'] = $public_post_types; | |
| 137 | 152 | } |
| 153 | + | |
| 138 | 154 | if (!empty($include)) { |
| 139 | 155 | $args['post__in'] = $include; |
| 140 | - $args['posts_per_page'] = count($include); | |
| 156 | + $args['posts_per_page'] = min(100, count($include)); | |
| 141 | 157 | } else { |
| 142 | - $args['posts_per_page'] = -1; | |
| 158 | + // Never run this unbounded: it is reachable by any 'edit_posts' user and | |
| 159 | + // -1 returns every published post of every public post type. | |
| 160 | + $args['posts_per_page'] = 50; | |
| 143 | 161 | } |
| 144 | 162 | if ($searchText) { |
| 145 | 163 | $args['s'] = $searchText; |
| 146 | 164 | } |
| @@ -200,8 +218,10 @@ | ||
| 200 | 218 | $search_text = $this->getSearchQuery(); |
| 201 | 219 | $taxonomies = $this->getAllPublicTaxonomies(); |
| 202 | 220 | $include = $this->getselecedIds(); |
| 203 | 221 | |
| 222 | + $post_type = ''; | |
| 223 | + | |
| 204 | 224 | if ($this->getPostType() == '_ultimate_post_kit_pro_related_post_type') { |
| 205 | 225 | $post_type = $this->getAllPublicPostTypes(); |
| 206 | 226 | } elseif ($this->getPostType()) { |
| 207 | 227 | $post_type = $this->getPostType(); |
| @@ -206,8 +226,12 @@ | ||
| 206 | 226 | } elseif ($this->getPostType()) { |
| 207 | 227 | $post_type = $this->getPostType(); |
| 208 | 228 | } |
| 209 | 229 | |
| 230 | + if (empty($post_type)) { | |
| 231 | + return []; | |
| 232 | + } | |
| 233 | + | |
| 210 | 234 | $post_taxonomies = get_object_taxonomies($post_type); |
| 211 | 235 | $taxonomies = array_intersect($post_taxonomies, $taxonomies); |
| 212 | 236 | $data = []; |
| 213 | 237 | |
| @@ -263,17 +287,34 @@ | ||
| 263 | 287 | |
| 264 | 288 | $args = [ |
| 265 | 289 | 'fields' => ['ID', 'display_name'], |
| 266 | 290 | 'orderby' => 'display_name', |
| 291 | + // Always bound the result set. Without this an empty search returns every | |
| 292 | + // user on the site, unpaged. | |
| 293 | + 'number' => 20, | |
| 267 | 294 | ]; |
| 268 | 295 | |
| 296 | + // This endpoint is only capability-gated on 'edit_posts', so a Contributor can | |
| 297 | + // reach it. WordPress core restricts callers without 'list_users' to users who | |
| 298 | + // have published something (see WP_REST_Users_Controller::get_items), so match | |
| 299 | + // that restriction rather than exposing the full user table. | |
| 300 | + if (!current_user_can('list_users')) { | |
| 301 | + $args['has_published_posts'] = true; | |
| 302 | + } | |
| 303 | + | |
| 269 | 304 | if (!empty($include)) { |
| 270 | 305 | $args['include'] = $include; |
| 306 | + // Resolving already-selected values needs room for all of them, but still | |
| 307 | + // bounded so a long id list cannot be used to dump the table. | |
| 308 | + $args['number'] = min(100, max(20, count($include))); | |
| 271 | 309 | } |
| 272 | 310 | |
| 273 | 311 | if ($search_text) { |
| 274 | - $args['number'] = 20; | |
| 275 | 312 | $args['search'] = "*$search_text*"; |
| 313 | + // WP_User_Query searches user_email when the term contains "@", which turns | |
| 314 | + // this into an address oracle. Core strips user_email from the searchable | |
| 315 | + // columns for callers without 'list_users'; do the same here. | |
| 316 | + $args['search_columns'] = ['ID', 'user_login', 'user_nicename', 'display_name']; | |
| 276 | 317 | } |
| 277 | 318 | |
| 278 | 319 | $users = get_users($args); |
| 279 | 320 | |