PluginProbe
Elementor Website Builder – more than just a page builder / 3.30.0-beta1
Elementor Website Builder – more than just a page builder v3.30.0-beta1
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
elementor / modules / wp-rest / classes / post-query.php

post-query.php in Elementor Website Builder – more than just a page builder 3.30.0-beta1, at modules/wp-rest/classes/post-query.php

263 lines 7.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Elementor\Modules\WpRest\Classes;
4
5 use Elementor\Core\Utils\Collection;
6 use Elementor\Modules\GlobalClasses\Utils\Error_Builder;
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 exit; // Exit if accessed directly.
10 }
11
12 class Post_Query {
13 const MAX_RESPONSE_COUNT = 100;
14 const NAMESPACE = 'elementor/v1';
15 const ENDPOINT = 'post';
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
37 /**
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.
75 * @param \WP_Query $wp_query The WP_Query instance.
76 * @return string Modified search query.
77 */
78 public function customize_search( string $search_term, \WP_Query $wp_query ) {
79 $term = $wp_query->get( 'search_term' ) ?? '';
80 $is_custom_search = $wp_query->get( 'custom_search' ) ?? false;
81
82 if ( $is_custom_search && ! empty( $term ) ) {
83 $search_term .= ' AND (';
84 $search_term .= "post_title LIKE '%" . esc_sql( $term ) . "%' ";
85 $search_term .= "OR ID LIKE '%" . esc_sql( $term ) . "%')";
86 }
87
88 return $search_term;
89 }
90
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 /**
108 * @param \WP_REST_Request $request
109 * @return \WP_REST_Response
110 */
111 private function get_posts( \WP_REST_Request $request ) {
112 $params = $request->get_params();
113 $term = trim( $params[ self::SEARCH_TERM_KEY ] ?? '' );
114
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;
127 $validated_count = max( $requested_count, 1 );
128 $max_count = min( $validated_count, self::MAX_RESPONSE_COUNT );
129 $post_types = new Collection( get_post_types( [ 'public' => true ], 'object' ) );
130
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,
144 'suppress_filters' => false,
145 'custom_search' => true,
146 'search_term' => $term,
147 ] ) );
148
149 $this->remove_filter_to_customize_query();
150
151 return new \WP_REST_Response( [
152 'success' => true,
153 'data' => [
154 'value' => $posts
155 ->map( function ( $post ) use ( $keys_format_map, $post_types ) {
156 $post_object = (array) $post;
157
158 if ( isset( $post_object['post_type'] ) ) {
159 $post_object['post_type'] = $post_types->get( ( $post_object['post_type'] ) )->label;
160 }
161
162 return $this->translate_keys( $post_object, $keys_format_map );
163 } )
164 ->all(),
165 ],
166 ], 200 );
167 }
168
169 /**
170 * @return void
171 */
172 private function add_filter_to_customize_query() {
173 $priority = 10;
174 $accepted_args = 2;
175
176 add_filter( 'posts_search', [ $this, 'customize_search' ], $priority, $accepted_args );
177 }
178
179 /**
180 * @return void
181 */
182 private function remove_filter_to_customize_query() {
183 $priority = 10;
184 $accepted_args = 2;
185
186 remove_filter( 'posts_search', [ $this, 'customize_search' ], $priority, $accepted_args );
187 }
188
189 /**
190 * @return array
191 */
192 private function get_endpoint_registration_args() {
193 return [
194 self::EXCLUDED_POST_TYPE_KEYS => [
195 'description' => 'Post type to exclude',
196 'type' => [ 'array', 'string' ],
197 'required' => false,
198 'default' => self::FORBIDDEN_POST_TYPES,
199 'sanitize_callback' => fn ( ...$args ) => $this->sanitize_string_array( ...$args ),
200 ],
201 self::SEARCH_TERM_KEY => [
202 'description' => 'Posts to search',
203 'type' => 'string',
204 'required' => false,
205 'default' => '',
206 'sanitize_callback' => 'sanitize_text_field',
207 ],
208 self::POST_KEYS_CONVERSION_MAP => [
209 'description' => 'Specify keys to extract and convert, i.e. ["key_1" => "new_key_1"].',
210 'type' => [ 'array', 'string' ],
211 'required' => false,
212 'default' => [],
213 'sanitize_callback' => fn ( ...$args ) => $this->sanitize_string_array( ...$args ),
214 ],
215 self::MAX_COUNT_KEY => [
216 'description' => 'Max count of returned items',
217 'type' => 'number',
218 'required' => false,
219 'default' => self::MAX_RESPONSE_COUNT,
220 ],
221 ];
222 }
223
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();
238 }
239
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 }
249
250 $replaced = [];
251
252 foreach ( $item as $key => $value ) {
253 if ( ! isset( $dictionary[ $key ] ) ) {
254 continue;
255 }
256
257 $replaced[ $dictionary[ $key ] ] = $value;
258 }
259
260 return $replaced;
261 }
262 }
263