PluginProbe
Elementor Website Builder – more than just a page builder / 3.31.2
Elementor Website Builder – more than just a page builder v3.31.2
4.3.0-beta3 4.3.0-beta2 4.3.0-beta1 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.5 4.2.0-beta2 4.2.0-dev2 4.2.0-beta1 4.1.4 4.1.3 4.1.2 4.1.1 4.1.0 4.1.0-beta3 4.1.0-dev3 4.0.9 4.1.0-beta2 4.1.0-dev2 4.0.8 4.1.0-beta1 4.1.0-dev1 All 452 releases
← All changes | modules/wp-rest/classes/post-query.php +166 -170 4.3.0-beta13.31.2 View file →
@@ -2,49 +2,88 @@
2 2
3 3 namespace Elementor\Modules\WpRest\Classes;
4 4
5 5 use Elementor\Core\Utils\Collection;
6 -use Elementor\Modules\WpRest\Base\Query as Base;
6 +use Elementor\Modules\GlobalClasses\Utils\Error_Builder;
7 7
8 8 if ( ! defined( 'ABSPATH' ) ) {
9 9 exit; // Exit if accessed directly.
10 10 }
11 11
12 -class Post_Query extends Base {
12 +class Post_Query {
13 + const MAX_RESPONSE_COUNT = 100;
14 + const NAMESPACE = 'elementor/v1';
13 15 const ENDPOINT = 'post';
14 - const SEARCH_FILTER_ACCEPTED_ARGS = 2;
15 - const DEFAULT_FORBIDDEN_POST_TYPES = [ 'e-floating-buttons', 'e-landing-page', 'elementor_library', 'attachment', 'revision', 'nav_menu_item', 'custom_css', 'customize_changeset' ];
16 - const SEARCH_IN_CONTENT_KEY = 'search_in_content';
17 - const ALLOWED_KEYS_CONVERSION_MAP = [
18 - 'ID' => 'id',
19 - 'post_title' => 'label',
20 - 'post_type' => 'groupLabel',
21 - ];
22 16
17 + const EXCLUDED_POST_TYPE_KEYS = 'excluded_post_types';
18 + const SEARCH_TERM_KEY = 'term';
19 + const POST_KEYS_CONVERSION_MAP = 'post_keys_conversion_map';
20 + const MAX_COUNT_KEY = 'max_count';
21 + const NONCE_KEY = 'x_wp_nonce';
22 +
23 + const FORBIDDEN_POST_TYPES = [ 'e-floating-buttons', 'e-landing-page', 'elementor_library', 'attachment' ];
24 +
25 + public function register( bool $override_existing_endpoints = false ): void {
26 + register_rest_route( self::NAMESPACE, self::ENDPOINT, [
27 + [
28 + 'methods' => \WP_REST_Server::READABLE,
29 + 'permission_callback' => fn ( \WP_REST_Request $request ) => $this->validate_access_permission( $request ),
30 + 'args' => $this->get_endpoint_registration_args(),
31 + 'sanitize_callback' => 'esc_attr',
32 + 'callback' => fn ( \WP_REST_Request $request ) => $this->route_wrapper( fn() => $this->get_posts( $request ) ),
33 + ],
34 + ], $override_existing_endpoints );
35 + }
36 +
23 37 /**
24 - * @param string $search_term The original search query.
38 + * @param $args array{
39 + * excluded_post_types: array,
40 + * post_keys_conversion_map: array,
41 + * max_count: int,
42 + * } The query parameters
43 + * @return array The query parameters.
44 + */
45 + public static function build_query_params( array $args ): array {
46 + $allowed_keys = [ self::EXCLUDED_POST_TYPE_KEYS, self::POST_KEYS_CONVERSION_MAP, self::MAX_COUNT_KEY ];
47 + $keys_to_encode = [ self::EXCLUDED_POST_TYPE_KEYS, self::POST_KEYS_CONVERSION_MAP ];
48 +
49 + $params = [];
50 +
51 + foreach ( $args as $key => $value ) {
52 + if ( ! in_array( $key, $allowed_keys, true ) || ! isset( $value ) ) {
53 + continue;
54 + }
55 +
56 + if ( ! in_array( $key, $keys_to_encode, true ) ) {
57 + $params[ $key ] = $value;
58 + continue;
59 + }
60 +
61 + $params[ $key ] = wp_json_encode( $value );
62 + }
63 +
64 + return $params;
65 + }
66 +
67 + private function validate_access_permission( $request ): bool {
68 + $nonce = $request->get_header( self::NONCE_KEY );
69 +
70 + return current_user_can( 'edit_posts' ) && wp_verify_nonce( $nonce, 'wp_rest' );
71 + }
72 +
73 + /**
74 + * @param string $search_term The original search query.
25 75 * @param \WP_Query $wp_query The WP_Query instance.
26 76 * @return string Modified search query.
27 77 */
28 - public function customize_post_query( string $search_term, \WP_Query $wp_query ) {
78 + public function customize_search( string $search_term, \WP_Query $wp_query ) {
29 79 $term = $wp_query->get( 'search_term' ) ?? '';
30 80 $is_custom_search = $wp_query->get( 'custom_search' ) ?? false;
31 81
32 82 if ( $is_custom_search && ! empty( $term ) ) {
33 - $escaped = esc_sql( $term );
34 - $search_in_content = $wp_query->get( self::SEARCH_IN_CONTENT_KEY ) ?? false;
35 83 $search_term .= ' AND (';
36 - $search_term .= "post_title LIKE '%{$escaped}%'";
37 - if ( $search_in_content ) {
38 - $search_term .= " OR post_content LIKE '%{$escaped}%'";
39 - $search_term .= " OR post_excerpt LIKE '%{$escaped}%'";
40 - }
41 - if ( ctype_digit( $term ) ) {
42 - $search_term .= ' OR ID = ' . intval( $term );
43 - } else {
44 - $search_term .= " OR ID LIKE '%{$escaped}%'";
45 - }
46 - $search_term .= ')';
84 + $search_term .= "post_title LIKE '%" . esc_sql( $term ) . "%' ";
85 + $search_term .= "OR ID LIKE '%" . esc_sql( $term ) . "%')";
47 86 }
48 87
49 88 return $search_term;
50 89 }
@@ -49,83 +88,78 @@
49 88 return $search_term;
50 89 }
51 90
52 91 /**
92 + * @param callable $cb The route callback.
93 + * @return \WP_REST_Response | \WP_Error
94 + */
95 + private function route_wrapper( callable $cb ) {
96 + try {
97 + $response = $cb();
98 + } catch ( \Exception $e ) {
99 + return Error_Builder::make( $e->getCode() )
100 + ->set_message( $e->getMessage() )
101 + ->build();
102 + }
103 +
104 + return $response;
105 + }
106 +
107 + /**
53 108 * @param \WP_REST_Request $request
54 109 * @return \WP_REST_Response
55 110 */
56 - protected function get( \WP_REST_Request $request ) {
111 + private function get_posts( \WP_REST_Request $request ) {
57 112 $params = $request->get_params();
58 113 $term = trim( $params[ self::SEARCH_TERM_KEY ] ?? '' );
59 114
60 - $keys_format_map = $this->filter_keys_conversion_map(
61 - $params[ self::KEYS_CONVERSION_MAP_KEY ] ?? self::ALLOWED_KEYS_CONVERSION_MAP,
62 - self::ALLOWED_KEYS_CONVERSION_MAP
63 - );
64 - $requested_count = $params[ self::ITEMS_COUNT_KEY ] ?? 0;
115 + if ( empty( $term ) ) {
116 + return new \WP_REST_Response( [
117 + 'success' => true,
118 + 'data' => [
119 + 'value' => [],
120 + ],
121 + ], 200 );
122 + }
123 +
124 + $excluded_types = array_merge( self::FORBIDDEN_POST_TYPES, $params[ self::EXCLUDED_POST_TYPE_KEYS ] ?? [] );
125 + $keys_format_map = $params[ self::POST_KEYS_CONVERSION_MAP ];
126 + $requested_count = $params[ self::MAX_COUNT_KEY ] ?? 0;
65 127 $validated_count = max( $requested_count, 1 );
66 - $post_count = min( $validated_count, self::MAX_RESPONSE_COUNT );
67 - $is_public_only = $params[ self::IS_PUBLIC_KEY ] ?? true;
68 - $post_types = $this->get_post_types_from_params( $request );
128 + $max_count = min( $validated_count, self::MAX_RESPONSE_COUNT );
129 + $post_types = new Collection( get_post_types( [ 'public' => true ], 'object' ) );
69 130
70 - $query_args = [
71 - 'post_type' => array_keys( $post_types ),
72 - 'numberposts' => $post_count,
73 - 'suppress_filters' => false,
74 - 'custom_search' => true,
75 - 'post_status' => $is_public_only ? 'publish' : 'any',
76 - 'orderby' => 'modified',
77 - 'order' => 'DESC',
78 - ];
131 + $post_types = $post_types->filter( function ( $post_type ) use ( $excluded_types ) {
132 + return ! in_array( $post_type->name, $excluded_types, true );
133 + } );
79 134
80 - // for non-admins (contributors), filter by author for private posts
81 - if ( ! $is_public_only && ! current_user_can( 'read_private_posts' ) ) {
82 - $query_args['author'] = get_current_user_id();
83 - }
135 + $post_type_slugs = $post_types->map( function ( $post_type ) {
136 + return $post_type->name;
137 + } );
84 138
85 - if ( ! empty( $term ) ) {
86 - $query_args['search_term'] = $term;
87 - $query_args[ self::SEARCH_IN_CONTENT_KEY ] = $params[ self::SEARCH_IN_CONTENT_KEY ] ?? false;
88 - }
139 + $this->add_filter_to_customize_query();
89 140
90 - if ( ! empty( $params[ self::META_QUERY_KEY ] ) && is_array( $params[ self::META_QUERY_KEY ] ) ) {
91 - $query_args['meta_query'] = $params[ self::META_QUERY_KEY ];
92 - }
141 + $posts = new Collection( get_posts( [
142 + 'post_type' => $post_type_slugs->all(),
143 + 'numberposts' => $max_count,
144 + 'suppress_filters' => false,
145 + 'custom_search' => true,
146 + 'search_term' => $term,
147 + ] ) );
93 148
94 - if ( ! empty( $params[ self::TAX_QUERY_KEY ] ) && is_array( $params[ self::TAX_QUERY_KEY ] ) ) {
95 - $query_args['tax_query'] = $params[ self::TAX_QUERY_KEY ];
96 - }
97 -
98 - $this->add_filter_to_customize_query();
99 - $posts = new Collection( get_posts( $query_args ) );
100 149 $this->remove_filter_to_customize_query();
101 150
102 - $post_type_labels = ( new Collection( $post_types ) )
103 - ->map( function ( $pt ) {
104 - return $pt->label;
105 - } )
106 - ->all();
107 -
108 151 return new \WP_REST_Response( [
109 152 'success' => true,
110 153 'data' => [
111 154 'value' => $posts
112 - ->filter( function ( $post ) {
113 - return current_user_can( 'read_post', $post->ID );
114 - } )
115 - ->map( function ( $post ) use ( $keys_format_map, $post_type_labels ) {
116 - $post_type_label = $post->post_type;
155 + ->map( function ( $post ) use ( $keys_format_map, $post_types ) {
156 + $post_object = (array) $post;
117 157
118 - if ( isset( $post_type_labels[ $post->post_type ] ) ) {
119 - $post_type_label = $post_type_labels[ $post->post_type ];
158 + if ( isset( $post_object['post_type'] ) ) {
159 + $post_object['post_type'] = $post_types->get( ( $post_object['post_type'] ) )->label;
120 160 }
121 161
122 - $post_object = [
123 - 'ID' => $post->ID,
124 - 'post_title' => $post->post_title,
125 - 'post_type' => $post_type_label,
126 - ];
127 -
128 162 return $this->translate_keys( $post_object, $keys_format_map );
129 163 } )
130 164 ->all(),
131 165 ],
@@ -135,12 +169,12 @@
135 169 /**
136 170 * @return void
137 171 */
138 172 private function add_filter_to_customize_query() {
139 - $priority = self::SEARCH_FILTER_PRIORITY;
140 - $accepted_args = self::SEARCH_FILTER_ACCEPTED_ARGS;
173 + $priority = 10;
174 + $accepted_args = 2;
141 175
142 - add_filter( 'posts_search', [ $this, 'customize_post_query' ], $priority, $accepted_args );
176 + add_filter( 'posts_search', [ $this, 'customize_search' ], $priority, $accepted_args );
143 177 }
144 178
145 179 /**
146 180 * @return void
@@ -145,33 +179,25 @@
145 179 /**
146 180 * @return void
147 181 */
148 182 private function remove_filter_to_customize_query() {
149 - $priority = self::SEARCH_FILTER_PRIORITY;
150 - $accepted_args = self::SEARCH_FILTER_ACCEPTED_ARGS;
183 + $priority = 10;
184 + $accepted_args = 2;
151 185
152 - remove_filter( 'posts_search', [ $this, 'customize_post_query' ], $priority, $accepted_args );
186 + remove_filter( 'posts_search', [ $this, 'customize_search' ], $priority, $accepted_args );
153 187 }
154 188
155 - protected function permission_check( \WP_REST_Request $request ): bool {
156 - return current_user_can( 'edit_posts' );
157 - }
158 -
159 - protected function get_endpoint_registration_args(): array {
189 + /**
190 + * @return array
191 + */
192 + private function get_endpoint_registration_args() {
160 193 return [
161 - self::INCLUDED_TYPE_KEY => [
162 - 'description' => 'Included post types',
163 - 'type' => 'array',
164 - 'required' => false,
165 - 'default' => null,
166 - 'sanitize_callback' => fn ( ...$args ) => self::sanitize_string_array( ...$args ),
167 - ],
168 - self::EXCLUDED_TYPE_KEY => [
194 + self::EXCLUDED_POST_TYPE_KEYS => [
169 195 'description' => 'Post type to exclude',
170 - 'type' => 'array',
196 + 'type' => [ 'array', 'string' ],
171 197 'required' => false,
172 - 'default' => self::DEFAULT_FORBIDDEN_POST_TYPES,
173 - 'sanitize_callback' => fn ( ...$args ) => self::sanitize_string_array( ...$args ),
198 + 'default' => self::FORBIDDEN_POST_TYPES,
199 + 'sanitize_callback' => fn ( ...$args ) => $this->sanitize_string_array( ...$args ),
174 200 ],
175 201 self::SEARCH_TERM_KEY => [
176 202 'description' => 'Posts to search',
177 203 'type' => 'string',
@@ -178,89 +204,59 @@
178 204 'required' => false,
179 205 'default' => '',
180 206 'sanitize_callback' => 'sanitize_text_field',
181 207 ],
182 - self::KEYS_CONVERSION_MAP_KEY => [
208 + self::POST_KEYS_CONVERSION_MAP => [
183 209 'description' => 'Specify keys to extract and convert, i.e. ["key_1" => "new_key_1"].',
184 - 'type' => 'array',
210 + 'type' => [ 'array', 'string' ],
185 211 'required' => false,
186 - 'default' => [
187 - 'ID' => 'id',
188 - 'post_title' => 'label',
189 - 'post_type' => 'groupLabel',
190 - ],
191 - 'sanitize_callback' => fn ( ...$args ) => self::sanitize_string_array( ...$args ),
212 + 'default' => [],
213 + 'sanitize_callback' => fn ( ...$args ) => $this->sanitize_string_array( ...$args ),
192 214 ],
193 - self::ITEMS_COUNT_KEY => [
194 - 'description' => 'Posts per page',
195 - 'type' => 'integer',
215 + self::MAX_COUNT_KEY => [
216 + 'description' => 'Max count of returned items',
217 + 'type' => 'number',
196 218 'required' => false,
197 219 'default' => self::MAX_RESPONSE_COUNT,
198 220 ],
199 - self::IS_PUBLIC_KEY => [
200 - 'description' => 'Whether to include only public post types',
201 - 'type' => 'boolean',
202 - 'required' => false,
203 - 'default' => true,
204 - ],
205 - self::META_QUERY_KEY => [
206 - 'description' => 'WP_Query meta_query array',
207 - 'type' => 'array',
208 - 'required' => false,
209 - 'default' => null,
210 - 'sanitize_callback' => fn ( ...$args ) => self::sanitize_string_array( ...$args ),
211 - ],
212 - self::TAX_QUERY_KEY => [
213 - 'description' => 'WP_Query tax_query array',
214 - 'type' => 'array',
215 - 'required' => false,
216 - 'default' => null,
217 - 'sanitize_callback' => fn ( ...$args ) => self::sanitize_string_array( ...$args ),
218 - ],
219 - self::SEARCH_IN_CONTENT_KEY => [
220 - 'description' => 'Whether to search within post content and excerpt in addition to title',
221 - 'type' => 'boolean',
222 - 'required' => false,
223 - 'default' => false,
224 - ],
225 221 ];
226 222 }
227 223
228 - protected static function get_allowed_param_keys(): array {
229 - return [
230 - self::EXCLUDED_TYPE_KEY,
231 - self::INCLUDED_TYPE_KEY,
232 - self::KEYS_CONVERSION_MAP_KEY,
233 - self::META_QUERY_KEY,
234 - self::TAX_QUERY_KEY,
235 - self::IS_PUBLIC_KEY,
236 - self::ITEMS_COUNT_KEY,
237 - self::SEARCH_IN_CONTENT_KEY,
238 - ];
224 + /**
225 + * @param Array<string>|string $input The input data, expected to be an array or JSON-encoded string.
226 + * @return array The sanitized array of strings.
227 + */
228 + private function sanitize_string_array( $input ) {
229 + if ( ! is_array( $input ) ) {
230 + $input = json_decode( sanitize_text_field( $input ) ) ?? [];
231 + }
232 +
233 + $array = new Collection( json_decode( json_encode( $input ), true ) );
234 +
235 + return $array
236 + ->map( 'sanitize_text_field' )
237 + ->all();
239 238 }
240 239
241 - protected static function get_keys_to_encode(): array {
242 - return [
243 - self::EXCLUDED_TYPE_KEY,
244 - self::INCLUDED_TYPE_KEY,
245 - self::KEYS_CONVERSION_MAP_KEY,
246 - self::META_QUERY_KEY,
247 - self::TAX_QUERY_KEY,
248 - ];
249 - }
240 + /**
241 + * @param array $item The input array with original keys.
242 + * @param array $dictionary An associative array mapping old keys to new keys.
243 + * @return array The array with translated keys.
244 + */
245 + private function translate_keys( array $item, array $dictionary ): array {
246 + if ( empty( $dictionary ) ) {
247 + return $item;
248 + }
250 249
251 - private function get_post_types_from_params( \WP_REST_Request $request ) {
252 - $included_types = $request->get_param( self::INCLUDED_TYPE_KEY );
253 - $excluded_types = $request->get_param( self::EXCLUDED_TYPE_KEY );
254 - $post_type_query_args = [
255 - 'public' => true,
256 - ];
250 + $replaced = [];
257 251
258 - $post_types = get_post_types( $post_type_query_args, 'objects' );
252 + foreach ( $item as $key => $value ) {
253 + if ( ! isset( $dictionary[ $key ] ) ) {
254 + continue;
255 + }
259 256
260 - return Collection::make( $post_types )
261 - ->filter( function ( $slug, $post_type ) use ( $included_types, $excluded_types ) {
262 - return ( empty( $included_types ) || in_array( $post_type, $included_types ) ) &&
263 - ( empty( $excluded_types ) || ! in_array( $post_type, $excluded_types ) );
264 - } )->all();
257 + $replaced[ $dictionary[ $key ] ] = $value;
258 + }
259 +
260 + return $replaced;
265 261 }
266 262 }