PluginProbe
Elementor Website Builder – more than just a page builder / 3.29.0-beta4
Elementor Website Builder – more than just a page builder v3.29.0-beta4
4.3.2 4.3.1 4.3.0 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 All 455 releases
← All changes | modules/wp-rest/classes/post-query.php +158 -163 4.2.0-beta1 → 3.29.0-beta4 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,12 +88,28 @@
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 115 if ( empty( $term ) ) {
@@ -65,68 +120,46 @@
65 120 ],
66 121 ], 200 );
67 122 }
68 123
69 - $keys_format_map = $this->filter_keys_conversion_map(
70 - $params[ self::KEYS_CONVERSION_MAP_KEY ] ?? self::ALLOWED_KEYS_CONVERSION_MAP,
71 - self::ALLOWED_KEYS_CONVERSION_MAP
72 - );
73 - $requested_count = $params[ self::ITEMS_COUNT_KEY ] ?? 0;
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;
74 127 $validated_count = max( $requested_count, 1 );
75 - $post_count = min( $validated_count, self::MAX_RESPONSE_COUNT );
76 - $is_public_only = $params[ self::IS_PUBLIC_KEY ] ?? true;
77 - $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' ) );
78 130
79 - $query_args = [
80 - 'post_type' => array_keys( $post_types ),
81 - 'numberposts' => $post_count,
82 - 'suppress_filters' => false,
83 - 'custom_search' => true,
84 - 'search_term' => $term,
85 - self::SEARCH_IN_CONTENT_KEY => $params[ self::SEARCH_IN_CONTENT_KEY ] ?? false,
86 - 'post_status' => $is_public_only ? 'publish' : 'any',
87 - 'orderby' => 'ID',
88 - 'order' => 'ASC',
89 - ];
131 + $post_types = $post_types->filter( function ( $post_type ) use ( $excluded_types ) {
132 + return ! in_array( $post_type->name, $excluded_types, true );
133 + } );
90 134
91 - if ( ! empty( $params[ self::META_QUERY_KEY ] ) && is_array( $params[ self::META_QUERY_KEY ] ) ) {
92 - $query_args['meta_query'] = $params[ self::META_QUERY_KEY ];
93 - }
135 + $post_type_slugs = $post_types->map( function ( $post_type ) {
136 + return $post_type->name;
137 + } );
94 138
95 - if ( ! empty( $params[ self::TAX_QUERY_KEY ] ) && is_array( $params[ self::TAX_QUERY_KEY ] ) ) {
96 - $query_args['tax_query'] = $params[ self::TAX_QUERY_KEY ];
97 - }
139 + $this->add_filter_to_customize_query();
98 140
99 - $this->add_filter_to_customize_query();
100 - $posts = new Collection( get_posts( $query_args ) );
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 + ] ) );
148 +
101 149 $this->remove_filter_to_customize_query();
102 150
103 - $post_type_labels = ( new Collection( $post_types ) )
104 - ->map( function ( $pt ) {
105 - return $pt->label;
106 - } )
107 - ->all();
108 -
109 151 return new \WP_REST_Response( [
110 152 'success' => true,
111 153 'data' => [
112 154 'value' => $posts
113 - ->filter( function ( $post ) {
114 - return current_user_can( 'read_post', $post->ID );
115 - } )
116 - ->map( function ( $post ) use ( $keys_format_map, $post_type_labels ) {
117 - $post_type_label = $post->post_type;
155 + ->map( function ( $post ) use ( $keys_format_map, $post_types ) {
156 + $post_object = (array) $post;
118 157
119 - if ( isset( $post_type_labels[ $post->post_type ] ) ) {
120 - $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;
121 160 }
122 161
123 - $post_object = [
124 - 'ID' => $post->ID,
125 - 'post_title' => $post->post_title,
126 - 'post_type' => $post_type_label,
127 - ];
128 -
129 162 return $this->translate_keys( $post_object, $keys_format_map );
130 163 } )
131 164 ->all(),
132 165 ],
@@ -136,12 +169,12 @@
136 169 /**
137 170 * @return void
138 171 */
139 172 private function add_filter_to_customize_query() {
140 - $priority = self::SEARCH_FILTER_PRIORITY;
141 - $accepted_args = self::SEARCH_FILTER_ACCEPTED_ARGS;
173 + $priority = 10;
174 + $accepted_args = 2;
142 175
143 - add_filter( 'posts_search', [ $this, 'customize_post_query' ], $priority, $accepted_args );
176 + add_filter( 'posts_search', [ $this, 'customize_search' ], $priority, $accepted_args );
144 177 }
145 178
146 179 /**
147 180 * @return void
@@ -146,33 +179,25 @@
146 179 /**
147 180 * @return void
148 181 */
149 182 private function remove_filter_to_customize_query() {
150 - $priority = self::SEARCH_FILTER_PRIORITY;
151 - $accepted_args = self::SEARCH_FILTER_ACCEPTED_ARGS;
183 + $priority = 10;
184 + $accepted_args = 2;
152 185
153 - remove_filter( 'posts_search', [ $this, 'customize_post_query' ], $priority, $accepted_args );
186 + remove_filter( 'posts_search', [ $this, 'customize_search' ], $priority, $accepted_args );
154 187 }
155 188
156 - protected function permission_check( \WP_REST_Request $request ): bool {
157 - return current_user_can( 'edit_posts' );
158 - }
159 -
160 - protected function get_endpoint_registration_args(): array {
189 + /**
190 + * @return array
191 + */
192 + private function get_endpoint_registration_args() {
161 193 return [
162 - self::INCLUDED_TYPE_KEY => [
163 - 'description' => 'Included post types',
164 - 'type' => 'array',
165 - 'required' => false,
166 - 'default' => null,
167 - 'sanitize_callback' => fn ( ...$args ) => self::sanitize_string_array( ...$args ),
168 - ],
169 - self::EXCLUDED_TYPE_KEY => [
194 + self::EXCLUDED_POST_TYPE_KEYS => [
170 195 'description' => 'Post type to exclude',
171 - 'type' => 'array',
196 + 'type' => [ 'array', 'string' ],
172 197 'required' => false,
173 - 'default' => self::DEFAULT_FORBIDDEN_POST_TYPES,
174 - '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 ),
175 200 ],
176 201 self::SEARCH_TERM_KEY => [
177 202 'description' => 'Posts to search',
178 203 'type' => 'string',
@@ -179,89 +204,59 @@
179 204 'required' => false,
180 205 'default' => '',
181 206 'sanitize_callback' => 'sanitize_text_field',
182 207 ],
183 - self::KEYS_CONVERSION_MAP_KEY => [
208 + self::POST_KEYS_CONVERSION_MAP => [
184 209 'description' => 'Specify keys to extract and convert, i.e. ["key_1" => "new_key_1"].',
185 - 'type' => 'array',
210 + 'type' => [ 'array', 'string' ],
186 211 'required' => false,
187 - 'default' => [
188 - 'ID' => 'id',
189 - 'post_title' => 'label',
190 - 'post_type' => 'groupLabel',
191 - ],
192 - 'sanitize_callback' => fn ( ...$args ) => self::sanitize_string_array( ...$args ),
212 + 'default' => [],
213 + 'sanitize_callback' => fn ( ...$args ) => $this->sanitize_string_array( ...$args ),
193 214 ],
194 - self::ITEMS_COUNT_KEY => [
195 - 'description' => 'Posts per page',
196 - 'type' => 'integer',
215 + self::MAX_COUNT_KEY => [
216 + 'description' => 'Max count of returned items',
217 + 'type' => 'number',
197 218 'required' => false,
198 219 'default' => self::MAX_RESPONSE_COUNT,
199 220 ],
200 - self::IS_PUBLIC_KEY => [
201 - 'description' => 'Whether to include only public post types',
202 - 'type' => 'boolean',
203 - 'required' => false,
204 - 'default' => true,
205 - ],
206 - self::META_QUERY_KEY => [
207 - 'description' => 'WP_Query meta_query array',
208 - 'type' => 'array',
209 - 'required' => false,
210 - 'default' => null,
211 - 'sanitize_callback' => fn ( ...$args ) => self::sanitize_string_array( ...$args ),
212 - ],
213 - self::TAX_QUERY_KEY => [
214 - 'description' => 'WP_Query tax_query array',
215 - 'type' => 'array',
216 - 'required' => false,
217 - 'default' => null,
218 - 'sanitize_callback' => fn ( ...$args ) => self::sanitize_string_array( ...$args ),
219 - ],
220 - self::SEARCH_IN_CONTENT_KEY => [
221 - 'description' => 'Whether to search within post content and excerpt in addition to title',
222 - 'type' => 'boolean',
223 - 'required' => false,
224 - 'default' => false,
225 - ],
226 221 ];
227 222 }
228 223
229 - protected static function get_allowed_param_keys(): array {
230 - return [
231 - self::EXCLUDED_TYPE_KEY,
232 - self::INCLUDED_TYPE_KEY,
233 - self::KEYS_CONVERSION_MAP_KEY,
234 - self::META_QUERY_KEY,
235 - self::TAX_QUERY_KEY,
236 - self::IS_PUBLIC_KEY,
237 - self::ITEMS_COUNT_KEY,
238 - self::SEARCH_IN_CONTENT_KEY,
239 - ];
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();
240 238 }
241 239
242 - protected static function get_keys_to_encode(): array {
243 - return [
244 - self::EXCLUDED_TYPE_KEY,
245 - self::INCLUDED_TYPE_KEY,
246 - self::KEYS_CONVERSION_MAP_KEY,
247 - self::META_QUERY_KEY,
248 - self::TAX_QUERY_KEY,
249 - ];
250 - }
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 + }
251 249
252 - private function get_post_types_from_params( \WP_REST_Request $request ) {
253 - $included_types = $request->get_param( self::INCLUDED_TYPE_KEY );
254 - $excluded_types = $request->get_param( self::EXCLUDED_TYPE_KEY );
255 - $post_type_query_args = [
256 - 'public' => true,
257 - ];
250 + $replaced = [];
258 251
259 - $post_types = get_post_types( $post_type_query_args, 'objects' );
252 + foreach ( $item as $key => $value ) {
253 + if ( ! isset( $dictionary[ $key ] ) ) {
254 + continue;
255 + }
260 256
261 - return Collection::make( $post_types )
262 - ->filter( function ( $slug, $post_type ) use ( $included_types, $excluded_types ) {
263 - return ( empty( $included_types ) || in_array( $post_type, $included_types ) ) &&
264 - ( empty( $excluded_types ) || ! in_array( $post_type, $excluded_types ) );
265 - } )->all();
257 + $replaced[ $dictionary[ $key ] ] = $value;
258 + }
259 +
260 + return $replaced;
266 261 }
267 262 }