PluginProbe
Elementor Website Builder – more than just a page builder / 3.32.0-dev3
Elementor Website Builder – more than just a page builder v3.32.0-dev3
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 4.0.7 All 451 releases
← All changes | modules/wp-rest/classes/post-query.php +155 -128 4.1.0-dev33.32.0-dev3 View file →
@@ -2,38 +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 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 +
17 37 /**
18 - * @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.
19 75 * @param \WP_Query $wp_query The WP_Query instance.
20 76 * @return string Modified search query.
21 77 */
22 - public function customize_post_query( string $search_term, \WP_Query $wp_query ) {
78 + public function customize_search( string $search_term, \WP_Query $wp_query ) {
23 79 $term = $wp_query->get( 'search_term' ) ?? '';
24 80 $is_custom_search = $wp_query->get( 'custom_search' ) ?? false;
25 81
26 82 if ( $is_custom_search && ! empty( $term ) ) {
27 - $escaped = esc_sql( $term );
28 83 $search_term .= ' AND (';
29 - $search_term .= "post_title LIKE '%{$escaped}%'";
30 - if ( ctype_digit( $term ) ) {
31 - $search_term .= ' OR ID = ' . intval( $term );
32 - } else {
33 - $search_term .= " OR ID LIKE '%{$escaped}%'";
34 - }
35 - $search_term .= ')';
84 + $search_term .= "post_title LIKE '%" . esc_sql( $term ) . "%' ";
85 + $search_term .= "OR ID LIKE '%" . esc_sql( $term ) . "%')";
36 86 }
37 87
38 88 return $search_term;
39 89 }
@@ -38,12 +88,28 @@
38 88 return $search_term;
39 89 }
40 90
41 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 + /**
42 108 * @param \WP_REST_Request $request
43 109 * @return \WP_REST_Response
44 110 */
45 - protected function get( \WP_REST_Request $request ) {
111 + private function get_posts( \WP_REST_Request $request ) {
46 112 $params = $request->get_params();
47 113 $term = trim( $params[ self::SEARCH_TERM_KEY ] ?? '' );
48 114
49 115 if ( empty( $term ) ) {
@@ -54,56 +120,44 @@
54 120 ],
55 121 ], 200 );
56 122 }
57 123
58 - $keys_format_map = $params[ self::KEYS_CONVERSION_MAP_KEY ];
59 - $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;
60 127 $validated_count = max( $requested_count, 1 );
61 - $post_count = min( $validated_count, self::MAX_RESPONSE_COUNT );
62 - $is_public_only = $params[ self::IS_PUBLIC_KEY ] ?? true;
63 - $post_types = $this->get_post_types_from_params( $params );
128 + $max_count = min( $validated_count, self::MAX_RESPONSE_COUNT );
129 + $post_types = new Collection( get_post_types( [ 'public' => true ], 'object' ) );
64 130
65 - $query_args = [
66 - 'post_type' => array_keys( $post_types ),
67 - 'numberposts' => $post_count,
131 + $post_types = $post_types->filter( function ( $post_type ) use ( $excluded_types ) {
132 + return ! in_array( $post_type->name, $excluded_types, true );
133 + } );
134 +
135 + $post_type_slugs = $post_types->map( function ( $post_type ) {
136 + return $post_type->name;
137 + } );
138 +
139 + $this->add_filter_to_customize_query();
140 +
141 + $posts = new Collection( get_posts( [
142 + 'post_type' => $post_type_slugs->all(),
143 + 'numberposts' => $max_count,
68 144 'suppress_filters' => false,
69 145 'custom_search' => true,
70 146 'search_term' => $term,
71 - 'post_status' => $is_public_only ? 'publish' : 'any',
72 - 'orderby' => 'ID',
73 - 'order' => 'ASC',
74 - ];
147 + ] ) );
75 148
76 - if ( ! empty( $params[ self::META_QUERY_KEY ] ) && is_array( $params[ self::META_QUERY_KEY ] ) ) {
77 - $query_args['meta_query'] = $params[ self::META_QUERY_KEY ];
78 - }
79 -
80 - if ( ! empty( $params[ self::TAX_QUERY_KEY ] ) && is_array( $params[ self::TAX_QUERY_KEY ] ) ) {
81 - $query_args['tax_query'] = $params[ self::TAX_QUERY_KEY ];
82 - }
83 -
84 - $this->add_filter_to_customize_query();
85 - $posts = new Collection( get_posts( $query_args ) );
86 149 $this->remove_filter_to_customize_query();
87 150
88 - $post_type_labels = ( new Collection( $post_types ) )
89 - ->map( function ( $pt ) {
90 - return $pt->label;
91 - } )
92 - ->all();
93 -
94 151 return new \WP_REST_Response( [
95 152 'success' => true,
96 153 'data' => [
97 154 'value' => $posts
98 - ->map( function ( $post ) use ( $keys_format_map, $post_type_labels ) {
155 + ->map( function ( $post ) use ( $keys_format_map, $post_types ) {
99 156 $post_object = (array) $post;
100 157
101 158 if ( isset( $post_object['post_type'] ) ) {
102 - $pt_name = $post_object['post_type'];
103 - if ( isset( $post_type_labels[ $pt_name ] ) ) {
104 - $post_object['post_type'] = $post_type_labels[ $pt_name ];
105 - }
159 + $post_object['post_type'] = $post_types->get( ( $post_object['post_type'] ) )->label;
106 160 }
107 161
108 162 return $this->translate_keys( $post_object, $keys_format_map );
109 163 } )
@@ -115,12 +169,12 @@
115 169 /**
116 170 * @return void
117 171 */
118 172 private function add_filter_to_customize_query() {
119 - $priority = self::SEARCH_FILTER_PRIORITY;
120 - $accepted_args = self::SEARCH_FILTER_ACCEPTED_ARGS;
173 + $priority = 10;
174 + $accepted_args = 2;
121 175
122 - add_filter( 'posts_search', [ $this, 'customize_post_query' ], $priority, $accepted_args );
176 + add_filter( 'posts_search', [ $this, 'customize_search' ], $priority, $accepted_args );
123 177 }
124 178
125 179 /**
126 180 * @return void
@@ -125,29 +179,25 @@
125 179 /**
126 180 * @return void
127 181 */
128 182 private function remove_filter_to_customize_query() {
129 - $priority = self::SEARCH_FILTER_PRIORITY;
130 - $accepted_args = self::SEARCH_FILTER_ACCEPTED_ARGS;
183 + $priority = 10;
184 + $accepted_args = 2;
131 185
132 - remove_filter( 'posts_search', [ $this, 'customize_post_query' ], $priority, $accepted_args );
186 + remove_filter( 'posts_search', [ $this, 'customize_search' ], $priority, $accepted_args );
133 187 }
134 188
135 - protected function get_endpoint_registration_args(): array {
189 + /**
190 + * @return array
191 + */
192 + private function get_endpoint_registration_args() {
136 193 return [
137 - self::INCLUDED_TYPE_KEY => [
138 - 'description' => 'Included post types',
139 - 'type' => 'array',
140 - 'required' => false,
141 - 'default' => null,
142 - 'sanitize_callback' => fn ( ...$args ) => self::sanitize_string_array( ...$args ),
143 - ],
144 - self::EXCLUDED_TYPE_KEY => [
194 + self::EXCLUDED_POST_TYPE_KEYS => [
145 195 'description' => 'Post type to exclude',
146 - 'type' => 'array',
196 + 'type' => [ 'array', 'string' ],
147 197 'required' => false,
148 - 'default' => self::DEFAULT_FORBIDDEN_POST_TYPES,
149 - '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 ),
150 200 ],
151 201 self::SEARCH_TERM_KEY => [
152 202 'description' => 'Posts to search',
153 203 'type' => 'string',
@@ -154,82 +204,59 @@
154 204 'required' => false,
155 205 'default' => '',
156 206 'sanitize_callback' => 'sanitize_text_field',
157 207 ],
158 - self::KEYS_CONVERSION_MAP_KEY => [
208 + self::POST_KEYS_CONVERSION_MAP => [
159 209 'description' => 'Specify keys to extract and convert, i.e. ["key_1" => "new_key_1"].',
160 - 'type' => 'array',
210 + 'type' => [ 'array', 'string' ],
161 211 'required' => false,
162 - 'default' => [
163 - 'ID' => 'id',
164 - 'post_title' => 'label',
165 - 'post_type' => 'groupLabel',
166 - ],
167 - 'sanitize_callback' => fn ( ...$args ) => self::sanitize_string_array( ...$args ),
212 + 'default' => [],
213 + 'sanitize_callback' => fn ( ...$args ) => $this->sanitize_string_array( ...$args ),
168 214 ],
169 - self::ITEMS_COUNT_KEY => [
170 - 'description' => 'Posts per page',
171 - 'type' => 'integer',
215 + self::MAX_COUNT_KEY => [
216 + 'description' => 'Max count of returned items',
217 + 'type' => 'number',
172 218 'required' => false,
173 219 'default' => self::MAX_RESPONSE_COUNT,
174 220 ],
175 - self::IS_PUBLIC_KEY => [
176 - 'description' => 'Whether to include only public post types',
177 - 'type' => 'boolean',
178 - 'required' => false,
179 - 'default' => true,
180 - ],
181 - self::META_QUERY_KEY => [
182 - 'description' => 'WP_Query meta_query array',
183 - 'type' => 'array',
184 - 'required' => false,
185 - 'default' => null,
186 - 'sanitize_callback' => fn ( ...$args ) => self::sanitize_string_array( ...$args ),
187 - ],
188 - self::TAX_QUERY_KEY => [
189 - 'description' => 'WP_Query tax_query array',
190 - 'type' => 'array',
191 - 'required' => false,
192 - 'default' => null,
193 - 'sanitize_callback' => fn ( ...$args ) => self::sanitize_string_array( ...$args ),
194 - ],
195 221 ];
196 222 }
197 223
198 - protected static function get_allowed_param_keys(): array {
199 - return [
200 - self::EXCLUDED_TYPE_KEY,
201 - self::INCLUDED_TYPE_KEY,
202 - self::KEYS_CONVERSION_MAP_KEY,
203 - self::META_QUERY_KEY,
204 - self::TAX_QUERY_KEY,
205 - self::IS_PUBLIC_KEY,
206 - self::ITEMS_COUNT_KEY,
207 - ];
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();
208 238 }
209 239
210 - protected static function get_keys_to_encode(): array {
211 - return [
212 - self::EXCLUDED_TYPE_KEY,
213 - self::INCLUDED_TYPE_KEY,
214 - self::KEYS_CONVERSION_MAP_KEY,
215 - self::META_QUERY_KEY,
216 - self::TAX_QUERY_KEY,
217 - ];
218 - }
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 + }
219 249
220 - private function get_post_types_from_params( $params ) {
221 - $included_types = $params[ self::INCLUDED_TYPE_KEY ];
222 - $excluded_types = $params[ self::EXCLUDED_TYPE_KEY ];
223 - $post_type_query_args = [
224 - 'public' => true,
225 - ];
250 + $replaced = [];
226 251
227 - $post_types = get_post_types( $post_type_query_args, 'objects' );
252 + foreach ( $item as $key => $value ) {
253 + if ( ! isset( $dictionary[ $key ] ) ) {
254 + continue;
255 + }
228 256
229 - return Collection::make( $post_types )
230 - ->filter( function ( $slug, $post_type ) use ( $included_types, $excluded_types ) {
231 - return ( empty( $included_types ) || in_array( $post_type, $included_types ) ) &&
232 - ( empty( $excluded_types ) || ! in_array( $post_type, $excluded_types ) );
233 - } )->all();
257 + $replaced[ $dictionary[ $key ] ] = $value;
258 + }
259 +
260 + return $replaced;
234 261 }
235 262 }