@@ -24,1303 +24,1303 @@ |
||
24 | 24 | class EED_Core_Rest_Api extends \EED_Module |
25 | 25 | { |
26 | 26 | |
27 | - const ee_api_namespace = 'ee/v'; |
|
27 | + const ee_api_namespace = 'ee/v'; |
|
28 | 28 | |
29 | - const ee_api_namespace_for_regex = 'ee\/v([^/]*)\/'; |
|
30 | - |
|
31 | - const saved_routes_option_names = 'ee_core_routes'; |
|
32 | - |
|
33 | - /** |
|
34 | - * string used in _links response bodies to make them globally unique. |
|
35 | - * |
|
36 | - * @see http://v2.wp-api.org/extending/linking/ |
|
37 | - */ |
|
38 | - const ee_api_link_namespace = 'https://api.eventespresso.com/'; |
|
39 | - |
|
40 | - /** |
|
41 | - * @var CalculatedModelFields |
|
42 | - */ |
|
43 | - protected static $_field_calculator; |
|
44 | - |
|
45 | - |
|
46 | - |
|
47 | - /** |
|
48 | - * @return EED_Core_Rest_Api|EED_Module |
|
49 | - */ |
|
50 | - public static function instance() |
|
51 | - { |
|
52 | - self::$_field_calculator = new CalculatedModelFields(); |
|
53 | - return parent::get_instance(__CLASS__); |
|
54 | - } |
|
55 | - |
|
56 | - |
|
57 | - |
|
58 | - /** |
|
59 | - * set_hooks - for hooking into EE Core, other modules, etc |
|
60 | - * |
|
61 | - * @access public |
|
62 | - * @return void |
|
63 | - */ |
|
64 | - public static function set_hooks() |
|
65 | - { |
|
66 | - self::set_hooks_both(); |
|
67 | - } |
|
68 | - |
|
69 | - |
|
70 | - |
|
71 | - /** |
|
72 | - * set_hooks_admin - for hooking into EE Admin Core, other modules, etc |
|
73 | - * |
|
74 | - * @access public |
|
75 | - * @return void |
|
76 | - */ |
|
77 | - public static function set_hooks_admin() |
|
78 | - { |
|
79 | - self::set_hooks_both(); |
|
80 | - } |
|
81 | - |
|
82 | - |
|
83 | - |
|
84 | - public static function set_hooks_both() |
|
85 | - { |
|
86 | - add_action('rest_api_init', array('EED_Core_Rest_Api', 'register_routes'), 10); |
|
87 | - add_action('rest_api_init', array('EED_Core_Rest_Api', 'set_hooks_rest_api'), 5); |
|
88 | - add_filter('rest_route_data', array('EED_Core_Rest_Api', 'hide_old_endpoints'), 10, 2); |
|
89 | - add_filter('rest_index', |
|
90 | - array('EventEspresso\core\libraries\rest_api\controllers\model\Meta', 'filterEeMetadataIntoIndex')); |
|
91 | - EED_Core_Rest_Api::invalidate_cached_route_data_on_version_change(); |
|
92 | - } |
|
93 | - |
|
94 | - |
|
95 | - |
|
96 | - /** |
|
97 | - * sets up hooks which only need to be included as part of REST API requests; |
|
98 | - * other requests like to the frontend or admin etc don't need them |
|
99 | - * |
|
100 | - * @throws \EE_Error |
|
101 | - */ |
|
102 | - public static function set_hooks_rest_api() |
|
103 | - { |
|
104 | - //set hooks which account for changes made to the API |
|
105 | - EED_Core_Rest_Api::_set_hooks_for_changes(); |
|
106 | - } |
|
107 | - |
|
108 | - |
|
109 | - |
|
110 | - /** |
|
111 | - * public wrapper of _set_hooks_for_changes. |
|
112 | - * Loads all the hooks which make requests to old versions of the API |
|
113 | - * appear the same as they always did |
|
114 | - * |
|
115 | - * @throws EE_Error |
|
116 | - */ |
|
117 | - public static function set_hooks_for_changes() |
|
118 | - { |
|
119 | - self::_set_hooks_for_changes(); |
|
120 | - } |
|
121 | - |
|
122 | - |
|
123 | - |
|
124 | - /** |
|
125 | - * If the user appears to be using WP API basic auth, tell them (via a persistent |
|
126 | - * admin notice and an email) that we're going to remove it soon, so they should |
|
127 | - * replace it with application passwords. |
|
128 | - * |
|
129 | - * @throws InvalidDataTypeException |
|
130 | - */ |
|
131 | - public static function maybe_notify_of_basic_auth_removal() |
|
132 | - { |
|
133 | - if ( |
|
134 | - apply_filters( |
|
135 | - 'FHEE__EED_Core_Rest_Api__maybe_notify_of_basic_auth_removal__override', |
|
136 | - ! isset($_SERVER['PHP_AUTH_USER']) |
|
137 | - && ! isset($_SERVER['HTTP_AUTHORIZATION']) |
|
138 | - ) |
|
139 | - ) { |
|
140 | - //sure it's a WP API request, but they aren't using basic auth, so don't bother them |
|
141 | - return; |
|
142 | - } |
|
143 | - $message = sprintf( |
|
144 | - esc_html__( |
|
145 | - 'We noticed you\'re using the WP API, which is used by the Event Espresso 4 mobile apps. Because of security and compatibility concerns, we will soon be removing our default authentication mechanism, WP API Basic Auth, from Event Espresso. It is recommended you instead install the %1$sWP Application Passwords plugin%2$s and use it with the EE4 Mobile apps. See %3$sour mobile app documentation%2$s for more information. %4$sIf you have installed the WP API Basic Auth plugin separately, or are not using the Event Espresso 4 mobile apps, you can disregard this message.%4$sThe Event Espresso Team', |
|
146 | - 'event_espresso' |
|
147 | - ), |
|
148 | - '<a href="https://wordpress.org/plugins/application-passwords/">', |
|
149 | - '</a>', |
|
150 | - '<a href="https://eventespresso.com/wiki/ee4-event-apps/#authentication">', |
|
151 | - '<br/>' |
|
152 | - ); |
|
153 | - //ok they're using the WP API with Basic Auth |
|
154 | - new PersistentAdminNotice('using_basic_auth', $message); |
|
155 | - if ( ! get_option('ee_notified_admin_on_basic_auth_removal', false)) { |
|
156 | - add_option('ee_notified_admin_on_basic_auth_removal', true); |
|
157 | - //piggy back off EE_Error::set_content_type, which sets the content type to HTML |
|
158 | - add_filter('wp_mail_content_type', array('EE_Error', 'set_content_type')); |
|
159 | - //and send the message to the site admin too |
|
160 | - wp_mail( |
|
161 | - get_option('admin_email'), |
|
162 | - esc_html__('Notice of Removal of WP API Basic Auth From Event Espresso 4', 'event_espresso'), |
|
163 | - $message |
|
164 | - ); |
|
165 | - remove_filter('wp_mail_content_type', array('EE_Error', 'set_content_type')); |
|
166 | - } |
|
167 | - } |
|
168 | - |
|
169 | - |
|
170 | - |
|
171 | - /** |
|
172 | - * Loads all the hooks which make requests to old versions of the API |
|
173 | - * appear the same as they always did |
|
174 | - * |
|
175 | - * @throws EE_Error |
|
176 | - */ |
|
177 | - protected static function _set_hooks_for_changes() |
|
178 | - { |
|
179 | - $folder_contents = EEH_File::get_contents_of_folders(array(EE_LIBRARIES . 'rest_api' . DS . 'changes'), false); |
|
180 | - foreach ($folder_contents as $classname_in_namespace => $filepath) { |
|
181 | - //ignore the base parent class |
|
182 | - //and legacy named classes |
|
183 | - if ($classname_in_namespace === 'ChangesInBase' |
|
184 | - || strpos($classname_in_namespace, 'Changes_In_') === 0 |
|
185 | - ) { |
|
186 | - continue; |
|
187 | - } |
|
188 | - $full_classname = 'EventEspresso\core\libraries\rest_api\changes\\' . $classname_in_namespace; |
|
189 | - if (class_exists($full_classname)) { |
|
190 | - $instance_of_class = new $full_classname; |
|
191 | - if ($instance_of_class instanceof ChangesInBase) { |
|
192 | - $instance_of_class->setHooks(); |
|
193 | - } |
|
194 | - } |
|
195 | - } |
|
196 | - } |
|
197 | - |
|
198 | - |
|
199 | - |
|
200 | - /** |
|
201 | - * Filters the WP routes to add our EE-related ones. This takes a bit of time |
|
202 | - * so we actually prefer to only do it when an EE plugin is activated or upgraded |
|
203 | - * |
|
204 | - * @throws \EE_Error |
|
205 | - */ |
|
206 | - public static function register_routes() |
|
207 | - { |
|
208 | - foreach (EED_Core_Rest_Api::get_ee_route_data() as $namespace => $relative_routes) { |
|
209 | - foreach ($relative_routes as $relative_route => $data_for_multiple_endpoints) { |
|
210 | - /** |
|
211 | - * @var array $data_for_multiple_endpoints numerically indexed array |
|
212 | - * but can also contain route options like { |
|
213 | - * @type array $schema { |
|
214 | - * @type callable $schema_callback |
|
215 | - * @type array $callback_args arguments that will be passed to the callback, after the |
|
216 | - * WP_REST_Request of course |
|
217 | - * } |
|
218 | - * } |
|
219 | - */ |
|
220 | - //when registering routes, register all the endpoints' data at the same time |
|
221 | - $multiple_endpoint_args = array(); |
|
222 | - foreach ($data_for_multiple_endpoints as $endpoint_key => $data_for_single_endpoint) { |
|
223 | - /** |
|
224 | - * @var array $data_for_single_endpoint { |
|
225 | - * @type callable $callback |
|
226 | - * @type string methods |
|
227 | - * @type array args |
|
228 | - * @type array _links |
|
229 | - * @type array $callback_args arguments that will be passed to the callback, after the |
|
230 | - * WP_REST_Request of course |
|
231 | - * } |
|
232 | - */ |
|
233 | - //skip route options |
|
234 | - if (! is_numeric($endpoint_key)) { |
|
235 | - continue; |
|
236 | - } |
|
237 | - if (! isset($data_for_single_endpoint['callback'], $data_for_single_endpoint['methods'])) { |
|
238 | - throw new EE_Error( |
|
239 | - esc_html__( |
|
240 | - // @codingStandardsIgnoreStart |
|
241 | - 'Endpoint configuration data needs to have entries "callback" (callable) and "methods" (comma-separated list of accepts HTTP methods).', |
|
242 | - // @codingStandardsIgnoreEnd |
|
243 | - 'event_espresso') |
|
244 | - ); |
|
245 | - } |
|
246 | - $callback = $data_for_single_endpoint['callback']; |
|
247 | - $single_endpoint_args = array( |
|
248 | - 'methods' => $data_for_single_endpoint['methods'], |
|
249 | - 'args' => isset($data_for_single_endpoint['args']) ? $data_for_single_endpoint['args'] |
|
250 | - : array(), |
|
251 | - ); |
|
252 | - if (isset($data_for_single_endpoint['_links'])) { |
|
253 | - $single_endpoint_args['_links'] = $data_for_single_endpoint['_links']; |
|
254 | - } |
|
255 | - if (isset($data_for_single_endpoint['callback_args'])) { |
|
256 | - $callback_args = $data_for_single_endpoint['callback_args']; |
|
257 | - $single_endpoint_args['callback'] = function (\WP_REST_Request $request) use ( |
|
258 | - $callback, |
|
259 | - $callback_args |
|
260 | - ) { |
|
261 | - array_unshift($callback_args, $request); |
|
262 | - return call_user_func_array( |
|
263 | - $callback, |
|
264 | - $callback_args |
|
265 | - ); |
|
266 | - }; |
|
267 | - } else { |
|
268 | - $single_endpoint_args['callback'] = $data_for_single_endpoint['callback']; |
|
269 | - } |
|
270 | - $multiple_endpoint_args[] = $single_endpoint_args; |
|
271 | - } |
|
272 | - if (isset($data_for_multiple_endpoints['schema'])) { |
|
273 | - $schema_route_data = $data_for_multiple_endpoints['schema']; |
|
274 | - $schema_callback = $schema_route_data['schema_callback']; |
|
275 | - $callback_args = $schema_route_data['callback_args']; |
|
276 | - $multiple_endpoint_args['schema'] = function () use ($schema_callback, $callback_args) { |
|
277 | - return call_user_func_array( |
|
278 | - $schema_callback, |
|
279 | - $callback_args |
|
280 | - ); |
|
281 | - }; |
|
282 | - } |
|
283 | - register_rest_route( |
|
284 | - $namespace, |
|
285 | - $relative_route, |
|
286 | - $multiple_endpoint_args |
|
287 | - ); |
|
288 | - } |
|
289 | - } |
|
290 | - } |
|
291 | - |
|
292 | - |
|
293 | - |
|
294 | - /** |
|
295 | - * Checks if there was a version change or something that merits invalidating the cached |
|
296 | - * route data. If so, invalidates the cached route data so that it gets refreshed |
|
297 | - * next time the WP API is used |
|
298 | - */ |
|
299 | - public static function invalidate_cached_route_data_on_version_change() |
|
300 | - { |
|
301 | - if (EE_System::instance()->detect_req_type() !== EE_System::req_type_normal) { |
|
302 | - EED_Core_Rest_Api::invalidate_cached_route_data(); |
|
303 | - } |
|
304 | - foreach (EE_Registry::instance()->addons as $addon) { |
|
305 | - if ($addon instanceof EE_Addon && $addon->detect_req_type() !== EE_System::req_type_normal) { |
|
306 | - EED_Core_Rest_Api::invalidate_cached_route_data(); |
|
307 | - } |
|
308 | - } |
|
309 | - } |
|
310 | - |
|
311 | - |
|
312 | - |
|
313 | - /** |
|
314 | - * Removes the cached route data so it will get refreshed next time the WP API is used |
|
315 | - */ |
|
316 | - public static function invalidate_cached_route_data() |
|
317 | - { |
|
318 | - //delete the saved EE REST API routes |
|
319 | - foreach (EED_Core_Rest_Api::versions_served() as $version => $hidden) { |
|
320 | - delete_option(EED_Core_Rest_Api::saved_routes_option_names . $version); |
|
321 | - } |
|
322 | - } |
|
323 | - |
|
324 | - |
|
325 | - |
|
326 | - /** |
|
327 | - * Gets the EE route data |
|
328 | - * |
|
329 | - * @return array top-level key is the namespace, next-level key is the route and its value is array{ |
|
330 | - * @throws \EE_Error |
|
331 | - * @type string|array $callback |
|
332 | - * @type string $methods |
|
333 | - * @type boolean $hidden_endpoint |
|
334 | - * } |
|
335 | - */ |
|
336 | - public static function get_ee_route_data() |
|
337 | - { |
|
338 | - $ee_routes = array(); |
|
339 | - foreach (self::versions_served() as $version => $hidden_endpoints) { |
|
340 | - $ee_routes[self::ee_api_namespace . $version] = self::_get_ee_route_data_for_version( |
|
341 | - $version, |
|
342 | - $hidden_endpoints |
|
343 | - ); |
|
344 | - } |
|
345 | - return $ee_routes; |
|
346 | - } |
|
347 | - |
|
348 | - |
|
349 | - |
|
350 | - /** |
|
351 | - * Gets the EE route data from the wp options if it exists already, |
|
352 | - * otherwise re-generates it and saves it to the option |
|
353 | - * |
|
354 | - * @param string $version |
|
355 | - * @param boolean $hidden_endpoints |
|
356 | - * @return array |
|
357 | - * @throws \EE_Error |
|
358 | - */ |
|
359 | - protected static function _get_ee_route_data_for_version($version, $hidden_endpoints = false) |
|
360 | - { |
|
361 | - $ee_routes = get_option(self::saved_routes_option_names . $version, null); |
|
362 | - if (! $ee_routes || (defined('EE_REST_API_DEBUG_MODE') && EE_REST_API_DEBUG_MODE)) { |
|
363 | - $ee_routes = self::_save_ee_route_data_for_version($version, $hidden_endpoints); |
|
364 | - } |
|
365 | - return $ee_routes; |
|
366 | - } |
|
367 | - |
|
368 | - |
|
369 | - |
|
370 | - /** |
|
371 | - * Saves the EE REST API route data to a wp option and returns it |
|
372 | - * |
|
373 | - * @param string $version |
|
374 | - * @param boolean $hidden_endpoints |
|
375 | - * @return mixed|null |
|
376 | - * @throws \EE_Error |
|
377 | - */ |
|
378 | - protected static function _save_ee_route_data_for_version($version, $hidden_endpoints = false) |
|
379 | - { |
|
380 | - $instance = self::instance(); |
|
381 | - $routes = apply_filters( |
|
382 | - 'EED_Core_Rest_Api__save_ee_route_data_for_version__routes', |
|
383 | - array_replace_recursive( |
|
384 | - $instance->_get_config_route_data_for_version($version, $hidden_endpoints), |
|
385 | - $instance->_get_meta_route_data_for_version($version, $hidden_endpoints), |
|
386 | - $instance->_get_model_route_data_for_version($version, $hidden_endpoints), |
|
387 | - $instance->_get_rpc_route_data_for_version($version, $hidden_endpoints) |
|
388 | - ) |
|
389 | - ); |
|
390 | - $option_name = self::saved_routes_option_names . $version; |
|
391 | - if (get_option($option_name)) { |
|
392 | - update_option($option_name, $routes, true); |
|
393 | - } else { |
|
394 | - add_option($option_name, $routes, null, 'no'); |
|
395 | - } |
|
396 | - return $routes; |
|
397 | - } |
|
398 | - |
|
399 | - |
|
400 | - |
|
401 | - /** |
|
402 | - * Calculates all the EE routes and saves it to a WordPress option so we don't |
|
403 | - * need to calculate it on every request |
|
404 | - * |
|
405 | - * @deprecated since version 4.9.1 |
|
406 | - * @return void |
|
407 | - */ |
|
408 | - public static function save_ee_routes() |
|
409 | - { |
|
410 | - if (EE_Maintenance_Mode::instance()->models_can_query()) { |
|
411 | - $instance = self::instance(); |
|
412 | - $routes = apply_filters( |
|
413 | - 'EED_Core_Rest_Api__save_ee_routes__routes', |
|
414 | - array_replace_recursive( |
|
415 | - $instance->_register_config_routes(), |
|
416 | - $instance->_register_meta_routes(), |
|
417 | - $instance->_register_model_routes(), |
|
418 | - $instance->_register_rpc_routes() |
|
419 | - ) |
|
420 | - ); |
|
421 | - update_option(self::saved_routes_option_names, $routes, true); |
|
422 | - } |
|
423 | - } |
|
424 | - |
|
425 | - |
|
426 | - |
|
427 | - /** |
|
428 | - * Gets all the route information relating to EE models |
|
429 | - * |
|
430 | - * @return array @see get_ee_route_data |
|
431 | - * @deprecated since version 4.9.1 |
|
432 | - */ |
|
433 | - protected function _register_model_routes() |
|
434 | - { |
|
435 | - $model_routes = array(); |
|
436 | - foreach (self::versions_served() as $version => $hidden_endpoint) { |
|
437 | - $model_routes[EED_Core_Rest_Api::ee_api_namespace |
|
438 | - . $version] = $this->_get_config_route_data_for_version($version, $hidden_endpoint); |
|
439 | - } |
|
440 | - return $model_routes; |
|
441 | - } |
|
442 | - |
|
443 | - |
|
444 | - |
|
445 | - /** |
|
446 | - * Decides whether or not to add write endpoints for this model. |
|
447 | - * |
|
448 | - * Currently, this defaults to exclude all global tables and models |
|
449 | - * which would allow inserting WP core data (we don't want to duplicate |
|
450 | - * what WP API does, as it's unnecessary, extra work, and potentially extra bugs) |
|
451 | - * @param EEM_Base $model |
|
452 | - * @return bool |
|
453 | - */ |
|
454 | - public static function should_have_write_endpoints(EEM_Base $model) |
|
455 | - { |
|
456 | - if ($model->is_wp_core_model()){ |
|
457 | - return false; |
|
458 | - } |
|
459 | - foreach($model->get_tables() as $table){ |
|
460 | - if( $table->is_global()){ |
|
461 | - return false; |
|
462 | - } |
|
463 | - } |
|
464 | - return true; |
|
465 | - } |
|
466 | - |
|
467 | - |
|
468 | - |
|
469 | - /** |
|
470 | - * Gets the names of all models which should have plural routes (eg `ee/v4.8.36/events`) |
|
471 | - * in this versioned namespace of EE4 |
|
472 | - * @param $version |
|
473 | - * @return array keys are model names (eg 'Event') and values ar either classnames (eg 'EEM_Event') |
|
474 | - */ |
|
475 | - public static function model_names_with_plural_routes($version){ |
|
476 | - $model_version_info = new ModelVersionInfo($version); |
|
477 | - $models_to_register = $model_version_info->modelsForRequestedVersion(); |
|
478 | - //let's not bother having endpoints for extra metas |
|
479 | - unset( |
|
480 | - $models_to_register['Extra_Meta'], |
|
481 | - $models_to_register['Extra_Join'], |
|
482 | - $models_to_register['Post_Meta'] |
|
483 | - ); |
|
484 | - return apply_filters( |
|
485 | - 'FHEE__EED_Core_REST_API___register_model_routes', |
|
486 | - $models_to_register |
|
487 | - ); |
|
488 | - } |
|
489 | - |
|
490 | - |
|
491 | - |
|
492 | - /** |
|
493 | - * Gets the route data for EE models in the specified version |
|
494 | - * |
|
495 | - * @param string $version |
|
496 | - * @param boolean $hidden_endpoint |
|
497 | - * @return array |
|
498 | - * @throws EE_Error |
|
499 | - */ |
|
500 | - protected function _get_model_route_data_for_version($version, $hidden_endpoint = false) |
|
501 | - { |
|
502 | - $model_routes = array(); |
|
503 | - $model_version_info = new ModelVersionInfo($version); |
|
504 | - foreach (EED_Core_Rest_Api::model_names_with_plural_routes($version) as $model_name => $model_classname) { |
|
505 | - $model = \EE_Registry::instance()->load_model($model_name); |
|
506 | - //if this isn't a valid model then let's skip iterate to the next item in the loop. |
|
507 | - if (! $model instanceof EEM_Base) { |
|
508 | - continue; |
|
509 | - } |
|
510 | - //yes we could just register one route for ALL models, but then they wouldn't show up in the index |
|
511 | - $plural_model_route = EED_Core_Rest_Api::get_collection_route($model); |
|
512 | - $singular_model_route = EED_Core_Rest_Api::get_entity_route($model, '(?P<id>[^\/]+)'); |
|
513 | - $model_routes[$plural_model_route] = array( |
|
514 | - array( |
|
515 | - 'callback' => array( |
|
516 | - 'EventEspresso\core\libraries\rest_api\controllers\model\Read', |
|
517 | - 'handleRequestGetAll', |
|
518 | - ), |
|
519 | - 'callback_args' => array($version, $model_name), |
|
520 | - 'methods' => WP_REST_Server::READABLE, |
|
521 | - 'hidden_endpoint' => $hidden_endpoint, |
|
522 | - 'args' => $this->_get_read_query_params($model, $version), |
|
523 | - '_links' => array( |
|
524 | - 'self' => rest_url(EED_Core_Rest_Api::ee_api_namespace . $version . $singular_model_route), |
|
525 | - ), |
|
526 | - ), |
|
527 | - 'schema' => array( |
|
528 | - 'schema_callback' => array( |
|
529 | - 'EventEspresso\core\libraries\rest_api\controllers\model\Read', |
|
530 | - 'handleSchemaRequest', |
|
531 | - ), |
|
532 | - 'callback_args' => array($version, $model_name), |
|
533 | - ), |
|
534 | - ); |
|
535 | - $model_routes[$singular_model_route] = array( |
|
536 | - array( |
|
537 | - 'callback' => array( |
|
538 | - 'EventEspresso\core\libraries\rest_api\controllers\model\Read', |
|
539 | - 'handleRequestGetOne', |
|
540 | - ), |
|
541 | - 'callback_args' => array($version, $model_name), |
|
542 | - 'methods' => WP_REST_Server::READABLE, |
|
543 | - 'hidden_endpoint' => $hidden_endpoint, |
|
544 | - 'args' => $this->_get_response_selection_query_params($model, $version), |
|
545 | - ), |
|
546 | - ); |
|
547 | - if( apply_filters( |
|
548 | - 'FHEE__EED_Core_Rest_Api___get_model_route_data_for_version__add_write_endpoints', |
|
549 | - EED_Core_Rest_Api::should_have_write_endpoints($model), |
|
550 | - $model |
|
551 | - )){ |
|
552 | - $model_routes[$plural_model_route][] = array( |
|
553 | - 'callback' => array( |
|
554 | - 'EventEspresso\core\libraries\rest_api\controllers\model\Write', |
|
555 | - 'handleRequestInsert', |
|
556 | - ), |
|
557 | - 'callback_args' => array($version, $model_name), |
|
558 | - 'methods' => WP_REST_Server::CREATABLE, |
|
559 | - 'hidden_endpoint' => $hidden_endpoint, |
|
560 | - 'args' => $this->_get_write_params($model_name, $model_version_info, true), |
|
561 | - ); |
|
562 | - $model_routes[$singular_model_route] = array_merge( |
|
563 | - $model_routes[$singular_model_route], |
|
564 | - array( |
|
565 | - array( |
|
566 | - 'callback' => array( |
|
567 | - 'EventEspresso\core\libraries\rest_api\controllers\model\Write', |
|
568 | - 'handleRequestUpdate', |
|
569 | - ), |
|
570 | - 'callback_args' => array($version, $model_name), |
|
571 | - 'methods' => WP_REST_Server::EDITABLE, |
|
572 | - 'hidden_endpoint' => $hidden_endpoint, |
|
573 | - 'args' => $this->_get_write_params($model_name, $model_version_info), |
|
574 | - ), |
|
575 | - array( |
|
576 | - 'callback' => array( |
|
577 | - 'EventEspresso\core\libraries\rest_api\controllers\model\Write', |
|
578 | - 'handleRequestDelete', |
|
579 | - ), |
|
580 | - 'callback_args' => array($version, $model_name), |
|
581 | - 'methods' => WP_REST_Server::DELETABLE, |
|
582 | - 'hidden_endpoint' => $hidden_endpoint, |
|
583 | - 'args' => $this->_get_delete_query_params($model, $version), |
|
584 | - ) |
|
585 | - ) |
|
586 | - ); |
|
587 | - } |
|
588 | - foreach ($model->relation_settings() as $relation_name => $relation_obj) { |
|
589 | - |
|
590 | - $related_route = EED_Core_Rest_Api::get_relation_route_via( |
|
591 | - $model, |
|
592 | - '(?P<id>[^\/]+)', |
|
593 | - $relation_obj |
|
594 | - ); |
|
595 | - $endpoints = array( |
|
596 | - array( |
|
597 | - 'callback' => array( |
|
598 | - 'EventEspresso\core\libraries\rest_api\controllers\model\Read', |
|
599 | - 'handleRequestGetRelated', |
|
600 | - ), |
|
601 | - 'callback_args' => array($version, $model_name, $relation_name), |
|
602 | - 'methods' => WP_REST_Server::READABLE, |
|
603 | - 'hidden_endpoint' => $hidden_endpoint, |
|
604 | - 'args' => $this->_get_read_query_params($relation_obj->get_other_model(), $version), |
|
605 | - ), |
|
606 | - ); |
|
607 | - $model_routes[$related_route] = $endpoints; |
|
608 | - } |
|
609 | - } |
|
610 | - return $model_routes; |
|
611 | - } |
|
612 | - |
|
613 | - |
|
614 | - |
|
615 | - /** |
|
616 | - * Gets the relative URI to a model's REST API plural route, after the EE4 versioned namespace, |
|
617 | - * excluding the preceding slash. |
|
618 | - * Eg you pass get_plural_route_to('Event') = 'events' |
|
619 | - * |
|
620 | - * @param EEM_Base $model |
|
621 | - * @return string |
|
622 | - */ |
|
623 | - public static function get_collection_route(EEM_Base $model) |
|
624 | - { |
|
625 | - return EEH_Inflector::pluralize_and_lower($model->get_this_model_name()); |
|
626 | - } |
|
627 | - |
|
628 | - |
|
629 | - |
|
630 | - /** |
|
631 | - * Gets the relative URI to a model's REST API singular route, after the EE4 versioned namespace, |
|
632 | - * excluding the preceding slash. |
|
633 | - * Eg you pass get_plural_route_to('Event', 12) = 'events/12' |
|
634 | - * |
|
635 | - * @param EEM_Base $model eg Event or Venue |
|
636 | - * @param string $id |
|
637 | - * @return string |
|
638 | - */ |
|
639 | - public static function get_entity_route($model, $id) |
|
640 | - { |
|
641 | - return EED_Core_Rest_Api::get_collection_route($model). '/' . $id; |
|
642 | - } |
|
643 | - |
|
644 | - |
|
645 | - /** |
|
646 | - * Gets the relative URI to a model's REST API singular route, after the EE4 versioned namespace, |
|
647 | - * excluding the preceding slash. |
|
648 | - * Eg you pass get_plural_route_to('Event', 12) = 'events/12' |
|
649 | - * |
|
650 | - * @param EEM_Base $model eg Event or Venue |
|
651 | - * @param string $id |
|
652 | - * @param EE_Model_Relation_Base $relation_obj |
|
653 | - * @return string |
|
654 | - */ |
|
655 | - public static function get_relation_route_via(EEM_Base $model, $id, EE_Model_Relation_Base $relation_obj) |
|
656 | - { |
|
657 | - $related_model_name_endpoint_part = ModelRead::getRelatedEntityName( |
|
658 | - $relation_obj->get_other_model()->get_this_model_name(), |
|
659 | - $relation_obj |
|
660 | - ); |
|
661 | - return EED_Core_Rest_Api::get_entity_route($model, $id) . '/' . $related_model_name_endpoint_part; |
|
662 | - } |
|
663 | - |
|
664 | - |
|
665 | - |
|
666 | - /** |
|
667 | - * Adds onto the $relative_route the EE4 REST API versioned namespace. |
|
668 | - * Eg if given '4.8.36' and 'events', will return 'ee/v4.8.36/events' |
|
669 | - * @param string $relative_route |
|
670 | - * @param string $version |
|
671 | - * @return string |
|
672 | - */ |
|
673 | - public static function get_versioned_route_to($relative_route, $version = '4.8.36'){ |
|
674 | - return '/' . EED_Core_Rest_Api::ee_api_namespace . $version . '/' . $relative_route; |
|
675 | - } |
|
676 | - |
|
677 | - |
|
678 | - |
|
679 | - /** |
|
680 | - * Adds all the RPC-style routes (remote procedure call-like routes, ie |
|
681 | - * routes that don't conform to the traditional REST CRUD-style). |
|
682 | - * |
|
683 | - * @deprecated since 4.9.1 |
|
684 | - */ |
|
685 | - protected function _register_rpc_routes() |
|
686 | - { |
|
687 | - $routes = array(); |
|
688 | - foreach (self::versions_served() as $version => $hidden_endpoint) { |
|
689 | - $routes[self::ee_api_namespace . $version] = $this->_get_rpc_route_data_for_version( |
|
690 | - $version, |
|
691 | - $hidden_endpoint |
|
692 | - ); |
|
693 | - } |
|
694 | - return $routes; |
|
695 | - } |
|
696 | - |
|
697 | - |
|
698 | - |
|
699 | - /** |
|
700 | - * @param string $version |
|
701 | - * @param boolean $hidden_endpoint |
|
702 | - * @return array |
|
703 | - */ |
|
704 | - protected function _get_rpc_route_data_for_version($version, $hidden_endpoint = false) |
|
705 | - { |
|
706 | - $this_versions_routes = array(); |
|
707 | - //checkin endpoint |
|
708 | - $this_versions_routes['registrations/(?P<REG_ID>\d+)/toggle_checkin_for_datetime/(?P<DTT_ID>\d+)'] = array( |
|
709 | - array( |
|
710 | - 'callback' => array( |
|
711 | - 'EventEspresso\core\libraries\rest_api\controllers\rpc\Checkin', |
|
712 | - 'handleRequestToggleCheckin', |
|
713 | - ), |
|
714 | - 'methods' => WP_REST_Server::CREATABLE, |
|
715 | - 'hidden_endpoint' => $hidden_endpoint, |
|
716 | - 'args' => array( |
|
717 | - 'force' => array( |
|
718 | - 'required' => false, |
|
719 | - 'default' => false, |
|
720 | - 'description' => __( |
|
721 | - // @codingStandardsIgnoreStart |
|
722 | - 'Whether to force toggle checkin, or to verify the registration status and allowed ticket uses', |
|
723 | - // @codingStandardsIgnoreEnd |
|
724 | - 'event_espresso' |
|
725 | - ), |
|
726 | - ), |
|
727 | - ), |
|
728 | - 'callback_args' => array($version), |
|
729 | - ), |
|
730 | - ); |
|
731 | - return apply_filters( |
|
732 | - 'FHEE__EED_Core_Rest_Api___register_rpc_routes__this_versions_routes', |
|
733 | - $this_versions_routes, |
|
734 | - $version, |
|
735 | - $hidden_endpoint |
|
736 | - ); |
|
737 | - } |
|
738 | - |
|
739 | - |
|
740 | - |
|
741 | - /** |
|
742 | - * Gets the query params that can be used when request one or many |
|
743 | - * |
|
744 | - * @param EEM_Base $model |
|
745 | - * @param string $version |
|
746 | - * @return array |
|
747 | - */ |
|
748 | - protected function _get_response_selection_query_params(\EEM_Base $model, $version) |
|
749 | - { |
|
750 | - return apply_filters( |
|
751 | - 'FHEE__EED_Core_Rest_Api___get_response_selection_query_params', |
|
752 | - array( |
|
753 | - 'include' => array( |
|
754 | - 'required' => false, |
|
755 | - 'default' => '*', |
|
756 | - 'type' => 'string', |
|
757 | - ), |
|
758 | - 'calculate' => array( |
|
759 | - 'required' => false, |
|
760 | - 'default' => '', |
|
761 | - 'enum' => self::$_field_calculator->retrieveCalculatedFieldsForModel($model), |
|
762 | - 'type' => 'string', |
|
763 | - //because we accept a CSV'd list of the enumerated strings, WP core validation and sanitization |
|
764 | - //freaks out. We'll just validate this argument while handling the request |
|
765 | - 'validate_callback' => null, |
|
766 | - 'sanitize_callback' => null, |
|
767 | - ), |
|
768 | - ), |
|
769 | - $model, |
|
770 | - $version |
|
771 | - ); |
|
772 | - } |
|
773 | - |
|
774 | - |
|
775 | - |
|
776 | - /** |
|
777 | - * Gets the parameters acceptable for delete requests |
|
778 | - * |
|
779 | - * @param \EEM_Base $model |
|
780 | - * @param string $version |
|
781 | - * @return array |
|
782 | - */ |
|
783 | - protected function _get_delete_query_params(\EEM_Base $model, $version) |
|
784 | - { |
|
785 | - $params_for_delete = array( |
|
786 | - 'allow_blocking' => array( |
|
787 | - 'required' => false, |
|
788 | - 'default' => true, |
|
789 | - 'type' => 'boolean', |
|
790 | - ), |
|
791 | - ); |
|
792 | - $params_for_delete['force'] = array( |
|
793 | - 'required' => false, |
|
794 | - 'default' => false, |
|
795 | - 'type' => 'boolean', |
|
796 | - ); |
|
797 | - return apply_filters( |
|
798 | - 'FHEE__EED_Core_Rest_Api___get_delete_query_params', |
|
799 | - $params_for_delete, |
|
800 | - $model, |
|
801 | - $version |
|
802 | - ); |
|
803 | - } |
|
804 | - |
|
805 | - |
|
806 | - |
|
807 | - /** |
|
808 | - * Gets info about reading query params that are acceptable |
|
809 | - * |
|
810 | - * @param \EEM_Base $model eg 'Event' or 'Venue' |
|
811 | - * @param string $version |
|
812 | - * @return array describing the args acceptable when querying this model |
|
813 | - * @throws EE_Error |
|
814 | - */ |
|
815 | - protected function _get_read_query_params(\EEM_Base $model, $version) |
|
816 | - { |
|
817 | - $default_orderby = array(); |
|
818 | - foreach ($model->get_combined_primary_key_fields() as $key_field) { |
|
819 | - $default_orderby[$key_field->get_name()] = 'ASC'; |
|
820 | - } |
|
821 | - return array_merge( |
|
822 | - $this->_get_response_selection_query_params($model, $version), |
|
823 | - array( |
|
824 | - 'where' => array( |
|
825 | - 'required' => false, |
|
826 | - 'default' => array(), |
|
827 | - 'type' => 'object', |
|
828 | - //because we accept an almost infinite list of possible where conditions, WP |
|
829 | - // core validation and sanitization freaks out. We'll just validate this argument |
|
830 | - // while handling the request |
|
831 | - 'validate_callback' => null, |
|
832 | - 'sanitize_callback' => null, |
|
833 | - ), |
|
834 | - 'limit' => array( |
|
835 | - 'required' => false, |
|
836 | - 'default' => EED_Core_Rest_Api::get_default_query_limit(), |
|
837 | - 'type' => array( |
|
838 | - 'array', |
|
839 | - 'string', |
|
840 | - 'integer', |
|
841 | - ), |
|
842 | - //because we accept a variety of types, WP core validation and sanitization |
|
843 | - //freaks out. We'll just validate this argument while handling the request |
|
844 | - 'validate_callback' => null, |
|
845 | - 'sanitize_callback' => null, |
|
846 | - ), |
|
847 | - 'order_by' => array( |
|
848 | - 'required' => false, |
|
849 | - 'default' => $default_orderby, |
|
850 | - 'type' => array( |
|
851 | - 'object', |
|
852 | - 'string', |
|
853 | - ),//because we accept a variety of types, WP core validation and sanitization |
|
854 | - //freaks out. We'll just validate this argument while handling the request |
|
855 | - 'validate_callback' => null, |
|
856 | - 'sanitize_callback' => null, |
|
857 | - ), |
|
858 | - 'group_by' => array( |
|
859 | - 'required' => false, |
|
860 | - 'default' => null, |
|
861 | - 'type' => array( |
|
862 | - 'object', |
|
863 | - 'string', |
|
864 | - ), |
|
865 | - //because we accept an almost infinite list of possible groupings, |
|
866 | - // WP core validation and sanitization |
|
867 | - //freaks out. We'll just validate this argument while handling the request |
|
868 | - 'validate_callback' => null, |
|
869 | - 'sanitize_callback' => null, |
|
870 | - ), |
|
871 | - 'having' => array( |
|
872 | - 'required' => false, |
|
873 | - 'default' => null, |
|
874 | - 'type' => 'object', |
|
875 | - //because we accept an almost infinite list of possible where conditions, WP |
|
876 | - // core validation and sanitization freaks out. We'll just validate this argument |
|
877 | - // while handling the request |
|
878 | - 'validate_callback' => null, |
|
879 | - 'sanitize_callback' => null, |
|
880 | - ), |
|
881 | - 'caps' => array( |
|
882 | - 'required' => false, |
|
883 | - 'default' => EEM_Base::caps_read, |
|
884 | - 'type' => 'string', |
|
885 | - 'enum' => array( |
|
886 | - EEM_Base::caps_read, |
|
887 | - EEM_Base::caps_read_admin, |
|
888 | - EEM_Base::caps_edit, |
|
889 | - EEM_Base::caps_delete |
|
890 | - ) |
|
891 | - ), |
|
892 | - ) |
|
893 | - ); |
|
894 | - } |
|
895 | - |
|
896 | - |
|
897 | - |
|
898 | - /** |
|
899 | - * Gets parameter information for a model regarding writing data |
|
900 | - * |
|
901 | - * @param string $model_name |
|
902 | - * @param ModelVersionInfo $model_version_info |
|
903 | - * @param boolean $create whether this is for request to create (in which case we need |
|
904 | - * all required params) or just to update (in which case we don't need those on every request) |
|
905 | - * @return array |
|
906 | - */ |
|
907 | - protected function _get_write_params( |
|
908 | - $model_name, |
|
909 | - ModelVersionInfo $model_version_info, |
|
910 | - $create = false |
|
911 | - ) { |
|
912 | - $model = EE_Registry::instance()->load_model($model_name); |
|
913 | - $fields = $model_version_info->fieldsOnModelInThisVersion($model); |
|
914 | - $args_info = array(); |
|
915 | - foreach ($fields as $field_name => $field_obj) { |
|
916 | - if ($field_obj->is_auto_increment()) { |
|
917 | - //totally ignore auto increment IDs |
|
918 | - continue; |
|
919 | - } |
|
920 | - $arg_info = $field_obj->getSchema(); |
|
921 | - $required = $create && ! $field_obj->is_nullable() && $field_obj->get_default_value() === null; |
|
922 | - $arg_info['required'] = $required; |
|
923 | - //remove the read-only flag. If it were read-only we wouldn't list it as an argument while writing, right? |
|
924 | - unset($arg_info['readonly']); |
|
925 | - $schema_properties = $field_obj->getSchemaProperties(); |
|
926 | - if ( |
|
927 | - isset($schema_properties['raw']) |
|
928 | - && $field_obj->getSchemaType() === 'object' |
|
929 | - ) { |
|
930 | - //if there's a "raw" form of this argument, use those properties instead |
|
931 | - $arg_info = array_replace( |
|
932 | - $arg_info, |
|
933 | - $schema_properties['raw'] |
|
934 | - ); |
|
935 | - } |
|
936 | - $arg_info['default'] = ModelDataTranslator::prepareFieldValueForJson( |
|
937 | - $field_obj, |
|
938 | - $field_obj->get_default_value(), |
|
939 | - $model_version_info->requestedVersion() |
|
940 | - ); |
|
941 | - //we do our own validation and sanitization within the controller |
|
942 | - $arg_info['sanitize_callback'] = |
|
943 | - array( |
|
944 | - 'EED_Core_Rest_Api', |
|
945 | - 'default_sanitize_callback', |
|
946 | - ); |
|
947 | - $args_info[$field_name] = $arg_info; |
|
948 | - if ($field_obj instanceof EE_Datetime_Field) { |
|
949 | - $gmt_arg_info = $arg_info; |
|
950 | - $gmt_arg_info['description'] = sprintf( |
|
951 | - esc_html__( |
|
952 | - '%1$s - the value for this field in UTC. Ignored if %2$s is provided.', |
|
953 | - 'event_espresso' |
|
954 | - ), |
|
955 | - $field_obj->get_nicename(), |
|
956 | - $field_name |
|
957 | - ); |
|
958 | - $args_info[$field_name . '_gmt'] = $gmt_arg_info; |
|
959 | - } |
|
960 | - } |
|
961 | - return $args_info; |
|
962 | - } |
|
963 | - |
|
964 | - |
|
965 | - |
|
966 | - /** |
|
967 | - * Replacement for WP API's 'rest_parse_request_arg'. |
|
968 | - * If the value is blank but not required, don't bother validating it. |
|
969 | - * Also, it uses our email validation instead of WP API's default. |
|
970 | - * |
|
971 | - * @param $value |
|
972 | - * @param WP_REST_Request $request |
|
973 | - * @param $param |
|
974 | - * @return bool|true|WP_Error |
|
975 | - * @throws InvalidArgumentException |
|
976 | - * @throws InvalidInterfaceException |
|
977 | - * @throws InvalidDataTypeException |
|
978 | - */ |
|
979 | - public static function default_sanitize_callback( $value, WP_REST_Request $request, $param) |
|
980 | - { |
|
981 | - $attributes = $request->get_attributes(); |
|
982 | - if (! isset($attributes['args'][$param]) |
|
983 | - || ! is_array($attributes['args'][$param])) { |
|
984 | - $validation_result = true; |
|
985 | - } else { |
|
986 | - $args = $attributes['args'][$param]; |
|
987 | - if (( |
|
988 | - $value === '' |
|
989 | - || $value === null |
|
990 | - ) |
|
991 | - && (! isset($args['required']) |
|
992 | - || $args['required'] === false |
|
993 | - ) |
|
994 | - ) { |
|
995 | - //not required and not provided? that's cool |
|
996 | - $validation_result = true; |
|
997 | - } elseif (isset($args['format']) |
|
998 | - && $args['format'] === 'email' |
|
999 | - ) { |
|
1000 | - $validation_result = true; |
|
1001 | - if (! self::_validate_email($value)) { |
|
1002 | - $validation_result = new WP_Error( |
|
1003 | - 'rest_invalid_param', |
|
1004 | - esc_html__( |
|
1005 | - 'The email address is not valid or does not exist.', |
|
1006 | - 'event_espresso' |
|
1007 | - ) |
|
1008 | - ); |
|
1009 | - } |
|
1010 | - } else { |
|
1011 | - $validation_result = rest_validate_value_from_schema($value, $args, $param); |
|
1012 | - } |
|
1013 | - } |
|
1014 | - if (is_wp_error($validation_result)) { |
|
1015 | - return $validation_result; |
|
1016 | - } |
|
1017 | - return rest_sanitize_request_arg($value, $request, $param); |
|
1018 | - } |
|
1019 | - |
|
1020 | - |
|
1021 | - |
|
1022 | - /** |
|
1023 | - * Returns whether or not this email address is valid. Copied from EE_Email_Validation_Strategy::_validate_email() |
|
1024 | - * |
|
1025 | - * @param $email |
|
1026 | - * @return bool |
|
1027 | - * @throws InvalidArgumentException |
|
1028 | - * @throws InvalidInterfaceException |
|
1029 | - * @throws InvalidDataTypeException |
|
1030 | - */ |
|
1031 | - protected static function _validate_email($email){ |
|
1032 | - try { |
|
1033 | - EmailAddressFactory::create($email); |
|
1034 | - return true; |
|
1035 | - } catch (EmailValidationException $e) { |
|
1036 | - return false; |
|
1037 | - } |
|
1038 | - } |
|
1039 | - |
|
1040 | - |
|
1041 | - |
|
1042 | - /** |
|
1043 | - * Gets routes for the config |
|
1044 | - * |
|
1045 | - * @return array @see _register_model_routes |
|
1046 | - * @deprecated since version 4.9.1 |
|
1047 | - */ |
|
1048 | - protected function _register_config_routes() |
|
1049 | - { |
|
1050 | - $config_routes = array(); |
|
1051 | - foreach (self::versions_served() as $version => $hidden_endpoint) { |
|
1052 | - $config_routes[self::ee_api_namespace . $version] = $this->_get_config_route_data_for_version( |
|
1053 | - $version, |
|
1054 | - $hidden_endpoint |
|
1055 | - ); |
|
1056 | - } |
|
1057 | - return $config_routes; |
|
1058 | - } |
|
1059 | - |
|
1060 | - |
|
1061 | - |
|
1062 | - /** |
|
1063 | - * Gets routes for the config for the specified version |
|
1064 | - * |
|
1065 | - * @param string $version |
|
1066 | - * @param boolean $hidden_endpoint |
|
1067 | - * @return array |
|
1068 | - */ |
|
1069 | - protected function _get_config_route_data_for_version($version, $hidden_endpoint) |
|
1070 | - { |
|
1071 | - return array( |
|
1072 | - 'config' => array( |
|
1073 | - array( |
|
1074 | - 'callback' => array( |
|
1075 | - 'EventEspresso\core\libraries\rest_api\controllers\config\Read', |
|
1076 | - 'handleRequest', |
|
1077 | - ), |
|
1078 | - 'methods' => WP_REST_Server::READABLE, |
|
1079 | - 'hidden_endpoint' => $hidden_endpoint, |
|
1080 | - 'callback_args' => array($version), |
|
1081 | - ), |
|
1082 | - ), |
|
1083 | - 'site_info' => array( |
|
1084 | - array( |
|
1085 | - 'callback' => array( |
|
1086 | - 'EventEspresso\core\libraries\rest_api\controllers\config\Read', |
|
1087 | - 'handleRequestSiteInfo', |
|
1088 | - ), |
|
1089 | - 'methods' => WP_REST_Server::READABLE, |
|
1090 | - 'hidden_endpoint' => $hidden_endpoint, |
|
1091 | - 'callback_args' => array($version), |
|
1092 | - ), |
|
1093 | - ), |
|
1094 | - ); |
|
1095 | - } |
|
1096 | - |
|
1097 | - |
|
1098 | - |
|
1099 | - /** |
|
1100 | - * Gets the meta info routes |
|
1101 | - * |
|
1102 | - * @return array @see _register_model_routes |
|
1103 | - * @deprecated since version 4.9.1 |
|
1104 | - */ |
|
1105 | - protected function _register_meta_routes() |
|
1106 | - { |
|
1107 | - $meta_routes = array(); |
|
1108 | - foreach (self::versions_served() as $version => $hidden_endpoint) { |
|
1109 | - $meta_routes[self::ee_api_namespace . $version] = $this->_get_meta_route_data_for_version( |
|
1110 | - $version, |
|
1111 | - $hidden_endpoint |
|
1112 | - ); |
|
1113 | - } |
|
1114 | - return $meta_routes; |
|
1115 | - } |
|
1116 | - |
|
1117 | - |
|
1118 | - |
|
1119 | - /** |
|
1120 | - * @param string $version |
|
1121 | - * @param boolean $hidden_endpoint |
|
1122 | - * @return array |
|
1123 | - */ |
|
1124 | - protected function _get_meta_route_data_for_version($version, $hidden_endpoint = false) |
|
1125 | - { |
|
1126 | - return array( |
|
1127 | - 'resources' => array( |
|
1128 | - array( |
|
1129 | - 'callback' => array( |
|
1130 | - 'EventEspresso\core\libraries\rest_api\controllers\model\Meta', |
|
1131 | - 'handleRequestModelsMeta', |
|
1132 | - ), |
|
1133 | - 'methods' => WP_REST_Server::READABLE, |
|
1134 | - 'hidden_endpoint' => $hidden_endpoint, |
|
1135 | - 'callback_args' => array($version), |
|
1136 | - ), |
|
1137 | - ), |
|
1138 | - ); |
|
1139 | - } |
|
1140 | - |
|
1141 | - |
|
1142 | - |
|
1143 | - /** |
|
1144 | - * Tries to hide old 4.6 endpoints from the |
|
1145 | - * |
|
1146 | - * @param array $route_data |
|
1147 | - * @return array |
|
1148 | - * @throws \EE_Error |
|
1149 | - */ |
|
1150 | - public static function hide_old_endpoints($route_data) |
|
1151 | - { |
|
1152 | - //allow API clients to override which endpoints get hidden, in case |
|
1153 | - //they want to discover particular endpoints |
|
1154 | - //also, we don't have access to the request so we have to just grab it from the superglobal |
|
1155 | - $force_show_ee_namespace = ltrim( |
|
1156 | - EEH_Array::is_set($_REQUEST, 'force_show_ee_namespace', ''), |
|
1157 | - '/' |
|
1158 | - ); |
|
1159 | - foreach (EED_Core_Rest_Api::get_ee_route_data() as $namespace => $relative_urls) { |
|
1160 | - foreach ($relative_urls as $resource_name => $endpoints) { |
|
1161 | - foreach ($endpoints as $key => $endpoint) { |
|
1162 | - //skip schema and other route options |
|
1163 | - if (! is_numeric($key)) { |
|
1164 | - continue; |
|
1165 | - } |
|
1166 | - //by default, hide "hidden_endpoint"s, unless the request indicates |
|
1167 | - //to $force_show_ee_namespace, in which case only show that one |
|
1168 | - //namespace's endpoints (and hide all others) |
|
1169 | - if ( |
|
1170 | - ($force_show_ee_namespace !== '' && $force_show_ee_namespace !== $namespace) |
|
1171 | - || ($endpoint['hidden_endpoint'] && $force_show_ee_namespace === '') |
|
1172 | - ) { |
|
1173 | - $full_route = '/' . ltrim($namespace, '/'); |
|
1174 | - $full_route .= '/' . ltrim($resource_name, '/'); |
|
1175 | - unset($route_data[$full_route]); |
|
1176 | - } |
|
1177 | - } |
|
1178 | - } |
|
1179 | - } |
|
1180 | - return $route_data; |
|
1181 | - } |
|
1182 | - |
|
1183 | - |
|
1184 | - |
|
1185 | - /** |
|
1186 | - * Returns an array describing which versions of core support serving requests for. |
|
1187 | - * Keys are core versions' major and minor version, and values are the |
|
1188 | - * LOWEST requested version they can serve. Eg, 4.7 can serve requests for 4.6-like |
|
1189 | - * data by just removing a few models and fields from the responses. However, 4.15 might remove |
|
1190 | - * the answers table entirely, in which case it would be very difficult for |
|
1191 | - * it to serve 4.6-style responses. |
|
1192 | - * Versions of core that are missing from this array are unknowns. |
|
1193 | - * previous ver |
|
1194 | - * |
|
1195 | - * @return array |
|
1196 | - */ |
|
1197 | - public static function version_compatibilities() |
|
1198 | - { |
|
1199 | - return apply_filters( |
|
1200 | - 'FHEE__EED_Core_REST_API__version_compatibilities', |
|
1201 | - array( |
|
1202 | - '4.8.29' => '4.8.29', |
|
1203 | - '4.8.33' => '4.8.29', |
|
1204 | - '4.8.34' => '4.8.29', |
|
1205 | - '4.8.36' => '4.8.29', |
|
1206 | - ) |
|
1207 | - ); |
|
1208 | - } |
|
1209 | - |
|
1210 | - |
|
1211 | - |
|
1212 | - /** |
|
1213 | - * Gets the latest API version served. Eg if there |
|
1214 | - * are two versions served of the API, 4.8.29 and 4.8.32, and |
|
1215 | - * we are on core version 4.8.34, it will return the string "4.8.32" |
|
1216 | - * |
|
1217 | - * @return string |
|
1218 | - */ |
|
1219 | - public static function latest_rest_api_version() |
|
1220 | - { |
|
1221 | - $versions_served = \EED_Core_Rest_Api::versions_served(); |
|
1222 | - $versions_served_keys = array_keys($versions_served); |
|
1223 | - return end($versions_served_keys); |
|
1224 | - } |
|
1225 | - |
|
1226 | - |
|
1227 | - |
|
1228 | - /** |
|
1229 | - * Using EED_Core_Rest_Api::version_compatibilities(), determines what version of |
|
1230 | - * EE the API can serve requests for. Eg, if we are on 4.15 of core, and |
|
1231 | - * we can serve requests from 4.12 or later, this will return array( '4.12', '4.13', '4.14', '4.15' ). |
|
1232 | - * We also indicate whether or not this version should be put in the index or not |
|
1233 | - * |
|
1234 | - * @return array keys are API version numbers (just major and minor numbers), and values |
|
1235 | - * are whether or not they should be hidden |
|
1236 | - */ |
|
1237 | - public static function versions_served() |
|
1238 | - { |
|
1239 | - $versions_served = array(); |
|
1240 | - $possibly_served_versions = EED_Core_Rest_Api::version_compatibilities(); |
|
1241 | - $lowest_compatible_version = end($possibly_served_versions); |
|
1242 | - reset($possibly_served_versions); |
|
1243 | - $versions_served_historically = array_keys($possibly_served_versions); |
|
1244 | - $latest_version = end($versions_served_historically); |
|
1245 | - reset($versions_served_historically); |
|
1246 | - //for each version of core we have ever served: |
|
1247 | - foreach ($versions_served_historically as $key_versioned_endpoint) { |
|
1248 | - //if it's not above the current core version, and it's compatible with the current version of core |
|
1249 | - if ($key_versioned_endpoint === $latest_version) { |
|
1250 | - //don't hide the latest version in the index |
|
1251 | - $versions_served[$key_versioned_endpoint] = false; |
|
1252 | - } elseif ( |
|
1253 | - $key_versioned_endpoint >= $lowest_compatible_version |
|
1254 | - && $key_versioned_endpoint < EED_Core_Rest_Api::core_version() |
|
1255 | - ) { |
|
1256 | - //include, but hide, previous versions which are still supported |
|
1257 | - $versions_served[$key_versioned_endpoint] = true; |
|
1258 | - } elseif (apply_filters( |
|
1259 | - 'FHEE__EED_Core_Rest_Api__versions_served__include_incompatible_versions', |
|
1260 | - false, |
|
1261 | - $possibly_served_versions |
|
1262 | - )) { |
|
1263 | - //if a version is no longer supported, don't include it in index or list of versions served |
|
1264 | - $versions_served[$key_versioned_endpoint] = true; |
|
1265 | - } |
|
1266 | - } |
|
1267 | - return $versions_served; |
|
1268 | - } |
|
1269 | - |
|
1270 | - |
|
1271 | - |
|
1272 | - /** |
|
1273 | - * Gets the major and minor version of EE core's version string |
|
1274 | - * |
|
1275 | - * @return string |
|
1276 | - */ |
|
1277 | - public static function core_version() |
|
1278 | - { |
|
1279 | - return apply_filters( |
|
1280 | - 'FHEE__EED_Core_REST_API__core_version', |
|
1281 | - implode( |
|
1282 | - '.', |
|
1283 | - array_slice( |
|
1284 | - explode( |
|
1285 | - '.', |
|
1286 | - espresso_version() |
|
1287 | - ), |
|
1288 | - 0, |
|
1289 | - 3 |
|
1290 | - ) |
|
1291 | - ) |
|
1292 | - ); |
|
1293 | - } |
|
1294 | - |
|
1295 | - |
|
1296 | - |
|
1297 | - /** |
|
1298 | - * Gets the default limit that should be used when querying for resources |
|
1299 | - * |
|
1300 | - * @return int |
|
1301 | - */ |
|
1302 | - public static function get_default_query_limit() |
|
1303 | - { |
|
1304 | - //we actually don't use a const because we want folks to always use |
|
1305 | - //this method, not the const directly |
|
1306 | - return apply_filters( |
|
1307 | - 'FHEE__EED_Core_Rest_Api__get_default_query_limit', |
|
1308 | - 50 |
|
1309 | - ); |
|
1310 | - } |
|
1311 | - |
|
1312 | - |
|
1313 | - |
|
1314 | - /** |
|
1315 | - * run - initial module setup |
|
1316 | - * |
|
1317 | - * @access public |
|
1318 | - * @param WP $WP |
|
1319 | - * @return void |
|
1320 | - */ |
|
1321 | - public function run($WP) |
|
1322 | - { |
|
1323 | - } |
|
29 | + const ee_api_namespace_for_regex = 'ee\/v([^/]*)\/'; |
|
30 | + |
|
31 | + const saved_routes_option_names = 'ee_core_routes'; |
|
32 | + |
|
33 | + /** |
|
34 | + * string used in _links response bodies to make them globally unique. |
|
35 | + * |
|
36 | + * @see http://v2.wp-api.org/extending/linking/ |
|
37 | + */ |
|
38 | + const ee_api_link_namespace = 'https://api.eventespresso.com/'; |
|
39 | + |
|
40 | + /** |
|
41 | + * @var CalculatedModelFields |
|
42 | + */ |
|
43 | + protected static $_field_calculator; |
|
44 | + |
|
45 | + |
|
46 | + |
|
47 | + /** |
|
48 | + * @return EED_Core_Rest_Api|EED_Module |
|
49 | + */ |
|
50 | + public static function instance() |
|
51 | + { |
|
52 | + self::$_field_calculator = new CalculatedModelFields(); |
|
53 | + return parent::get_instance(__CLASS__); |
|
54 | + } |
|
55 | + |
|
56 | + |
|
57 | + |
|
58 | + /** |
|
59 | + * set_hooks - for hooking into EE Core, other modules, etc |
|
60 | + * |
|
61 | + * @access public |
|
62 | + * @return void |
|
63 | + */ |
|
64 | + public static function set_hooks() |
|
65 | + { |
|
66 | + self::set_hooks_both(); |
|
67 | + } |
|
68 | + |
|
69 | + |
|
70 | + |
|
71 | + /** |
|
72 | + * set_hooks_admin - for hooking into EE Admin Core, other modules, etc |
|
73 | + * |
|
74 | + * @access public |
|
75 | + * @return void |
|
76 | + */ |
|
77 | + public static function set_hooks_admin() |
|
78 | + { |
|
79 | + self::set_hooks_both(); |
|
80 | + } |
|
81 | + |
|
82 | + |
|
83 | + |
|
84 | + public static function set_hooks_both() |
|
85 | + { |
|
86 | + add_action('rest_api_init', array('EED_Core_Rest_Api', 'register_routes'), 10); |
|
87 | + add_action('rest_api_init', array('EED_Core_Rest_Api', 'set_hooks_rest_api'), 5); |
|
88 | + add_filter('rest_route_data', array('EED_Core_Rest_Api', 'hide_old_endpoints'), 10, 2); |
|
89 | + add_filter('rest_index', |
|
90 | + array('EventEspresso\core\libraries\rest_api\controllers\model\Meta', 'filterEeMetadataIntoIndex')); |
|
91 | + EED_Core_Rest_Api::invalidate_cached_route_data_on_version_change(); |
|
92 | + } |
|
93 | + |
|
94 | + |
|
95 | + |
|
96 | + /** |
|
97 | + * sets up hooks which only need to be included as part of REST API requests; |
|
98 | + * other requests like to the frontend or admin etc don't need them |
|
99 | + * |
|
100 | + * @throws \EE_Error |
|
101 | + */ |
|
102 | + public static function set_hooks_rest_api() |
|
103 | + { |
|
104 | + //set hooks which account for changes made to the API |
|
105 | + EED_Core_Rest_Api::_set_hooks_for_changes(); |
|
106 | + } |
|
107 | + |
|
108 | + |
|
109 | + |
|
110 | + /** |
|
111 | + * public wrapper of _set_hooks_for_changes. |
|
112 | + * Loads all the hooks which make requests to old versions of the API |
|
113 | + * appear the same as they always did |
|
114 | + * |
|
115 | + * @throws EE_Error |
|
116 | + */ |
|
117 | + public static function set_hooks_for_changes() |
|
118 | + { |
|
119 | + self::_set_hooks_for_changes(); |
|
120 | + } |
|
121 | + |
|
122 | + |
|
123 | + |
|
124 | + /** |
|
125 | + * If the user appears to be using WP API basic auth, tell them (via a persistent |
|
126 | + * admin notice and an email) that we're going to remove it soon, so they should |
|
127 | + * replace it with application passwords. |
|
128 | + * |
|
129 | + * @throws InvalidDataTypeException |
|
130 | + */ |
|
131 | + public static function maybe_notify_of_basic_auth_removal() |
|
132 | + { |
|
133 | + if ( |
|
134 | + apply_filters( |
|
135 | + 'FHEE__EED_Core_Rest_Api__maybe_notify_of_basic_auth_removal__override', |
|
136 | + ! isset($_SERVER['PHP_AUTH_USER']) |
|
137 | + && ! isset($_SERVER['HTTP_AUTHORIZATION']) |
|
138 | + ) |
|
139 | + ) { |
|
140 | + //sure it's a WP API request, but they aren't using basic auth, so don't bother them |
|
141 | + return; |
|
142 | + } |
|
143 | + $message = sprintf( |
|
144 | + esc_html__( |
|
145 | + 'We noticed you\'re using the WP API, which is used by the Event Espresso 4 mobile apps. Because of security and compatibility concerns, we will soon be removing our default authentication mechanism, WP API Basic Auth, from Event Espresso. It is recommended you instead install the %1$sWP Application Passwords plugin%2$s and use it with the EE4 Mobile apps. See %3$sour mobile app documentation%2$s for more information. %4$sIf you have installed the WP API Basic Auth plugin separately, or are not using the Event Espresso 4 mobile apps, you can disregard this message.%4$sThe Event Espresso Team', |
|
146 | + 'event_espresso' |
|
147 | + ), |
|
148 | + '<a href="https://wordpress.org/plugins/application-passwords/">', |
|
149 | + '</a>', |
|
150 | + '<a href="https://eventespresso.com/wiki/ee4-event-apps/#authentication">', |
|
151 | + '<br/>' |
|
152 | + ); |
|
153 | + //ok they're using the WP API with Basic Auth |
|
154 | + new PersistentAdminNotice('using_basic_auth', $message); |
|
155 | + if ( ! get_option('ee_notified_admin_on_basic_auth_removal', false)) { |
|
156 | + add_option('ee_notified_admin_on_basic_auth_removal', true); |
|
157 | + //piggy back off EE_Error::set_content_type, which sets the content type to HTML |
|
158 | + add_filter('wp_mail_content_type', array('EE_Error', 'set_content_type')); |
|
159 | + //and send the message to the site admin too |
|
160 | + wp_mail( |
|
161 | + get_option('admin_email'), |
|
162 | + esc_html__('Notice of Removal of WP API Basic Auth From Event Espresso 4', 'event_espresso'), |
|
163 | + $message |
|
164 | + ); |
|
165 | + remove_filter('wp_mail_content_type', array('EE_Error', 'set_content_type')); |
|
166 | + } |
|
167 | + } |
|
168 | + |
|
169 | + |
|
170 | + |
|
171 | + /** |
|
172 | + * Loads all the hooks which make requests to old versions of the API |
|
173 | + * appear the same as they always did |
|
174 | + * |
|
175 | + * @throws EE_Error |
|
176 | + */ |
|
177 | + protected static function _set_hooks_for_changes() |
|
178 | + { |
|
179 | + $folder_contents = EEH_File::get_contents_of_folders(array(EE_LIBRARIES . 'rest_api' . DS . 'changes'), false); |
|
180 | + foreach ($folder_contents as $classname_in_namespace => $filepath) { |
|
181 | + //ignore the base parent class |
|
182 | + //and legacy named classes |
|
183 | + if ($classname_in_namespace === 'ChangesInBase' |
|
184 | + || strpos($classname_in_namespace, 'Changes_In_') === 0 |
|
185 | + ) { |
|
186 | + continue; |
|
187 | + } |
|
188 | + $full_classname = 'EventEspresso\core\libraries\rest_api\changes\\' . $classname_in_namespace; |
|
189 | + if (class_exists($full_classname)) { |
|
190 | + $instance_of_class = new $full_classname; |
|
191 | + if ($instance_of_class instanceof ChangesInBase) { |
|
192 | + $instance_of_class->setHooks(); |
|
193 | + } |
|
194 | + } |
|
195 | + } |
|
196 | + } |
|
197 | + |
|
198 | + |
|
199 | + |
|
200 | + /** |
|
201 | + * Filters the WP routes to add our EE-related ones. This takes a bit of time |
|
202 | + * so we actually prefer to only do it when an EE plugin is activated or upgraded |
|
203 | + * |
|
204 | + * @throws \EE_Error |
|
205 | + */ |
|
206 | + public static function register_routes() |
|
207 | + { |
|
208 | + foreach (EED_Core_Rest_Api::get_ee_route_data() as $namespace => $relative_routes) { |
|
209 | + foreach ($relative_routes as $relative_route => $data_for_multiple_endpoints) { |
|
210 | + /** |
|
211 | + * @var array $data_for_multiple_endpoints numerically indexed array |
|
212 | + * but can also contain route options like { |
|
213 | + * @type array $schema { |
|
214 | + * @type callable $schema_callback |
|
215 | + * @type array $callback_args arguments that will be passed to the callback, after the |
|
216 | + * WP_REST_Request of course |
|
217 | + * } |
|
218 | + * } |
|
219 | + */ |
|
220 | + //when registering routes, register all the endpoints' data at the same time |
|
221 | + $multiple_endpoint_args = array(); |
|
222 | + foreach ($data_for_multiple_endpoints as $endpoint_key => $data_for_single_endpoint) { |
|
223 | + /** |
|
224 | + * @var array $data_for_single_endpoint { |
|
225 | + * @type callable $callback |
|
226 | + * @type string methods |
|
227 | + * @type array args |
|
228 | + * @type array _links |
|
229 | + * @type array $callback_args arguments that will be passed to the callback, after the |
|
230 | + * WP_REST_Request of course |
|
231 | + * } |
|
232 | + */ |
|
233 | + //skip route options |
|
234 | + if (! is_numeric($endpoint_key)) { |
|
235 | + continue; |
|
236 | + } |
|
237 | + if (! isset($data_for_single_endpoint['callback'], $data_for_single_endpoint['methods'])) { |
|
238 | + throw new EE_Error( |
|
239 | + esc_html__( |
|
240 | + // @codingStandardsIgnoreStart |
|
241 | + 'Endpoint configuration data needs to have entries "callback" (callable) and "methods" (comma-separated list of accepts HTTP methods).', |
|
242 | + // @codingStandardsIgnoreEnd |
|
243 | + 'event_espresso') |
|
244 | + ); |
|
245 | + } |
|
246 | + $callback = $data_for_single_endpoint['callback']; |
|
247 | + $single_endpoint_args = array( |
|
248 | + 'methods' => $data_for_single_endpoint['methods'], |
|
249 | + 'args' => isset($data_for_single_endpoint['args']) ? $data_for_single_endpoint['args'] |
|
250 | + : array(), |
|
251 | + ); |
|
252 | + if (isset($data_for_single_endpoint['_links'])) { |
|
253 | + $single_endpoint_args['_links'] = $data_for_single_endpoint['_links']; |
|
254 | + } |
|
255 | + if (isset($data_for_single_endpoint['callback_args'])) { |
|
256 | + $callback_args = $data_for_single_endpoint['callback_args']; |
|
257 | + $single_endpoint_args['callback'] = function (\WP_REST_Request $request) use ( |
|
258 | + $callback, |
|
259 | + $callback_args |
|
260 | + ) { |
|
261 | + array_unshift($callback_args, $request); |
|
262 | + return call_user_func_array( |
|
263 | + $callback, |
|
264 | + $callback_args |
|
265 | + ); |
|
266 | + }; |
|
267 | + } else { |
|
268 | + $single_endpoint_args['callback'] = $data_for_single_endpoint['callback']; |
|
269 | + } |
|
270 | + $multiple_endpoint_args[] = $single_endpoint_args; |
|
271 | + } |
|
272 | + if (isset($data_for_multiple_endpoints['schema'])) { |
|
273 | + $schema_route_data = $data_for_multiple_endpoints['schema']; |
|
274 | + $schema_callback = $schema_route_data['schema_callback']; |
|
275 | + $callback_args = $schema_route_data['callback_args']; |
|
276 | + $multiple_endpoint_args['schema'] = function () use ($schema_callback, $callback_args) { |
|
277 | + return call_user_func_array( |
|
278 | + $schema_callback, |
|
279 | + $callback_args |
|
280 | + ); |
|
281 | + }; |
|
282 | + } |
|
283 | + register_rest_route( |
|
284 | + $namespace, |
|
285 | + $relative_route, |
|
286 | + $multiple_endpoint_args |
|
287 | + ); |
|
288 | + } |
|
289 | + } |
|
290 | + } |
|
291 | + |
|
292 | + |
|
293 | + |
|
294 | + /** |
|
295 | + * Checks if there was a version change or something that merits invalidating the cached |
|
296 | + * route data. If so, invalidates the cached route data so that it gets refreshed |
|
297 | + * next time the WP API is used |
|
298 | + */ |
|
299 | + public static function invalidate_cached_route_data_on_version_change() |
|
300 | + { |
|
301 | + if (EE_System::instance()->detect_req_type() !== EE_System::req_type_normal) { |
|
302 | + EED_Core_Rest_Api::invalidate_cached_route_data(); |
|
303 | + } |
|
304 | + foreach (EE_Registry::instance()->addons as $addon) { |
|
305 | + if ($addon instanceof EE_Addon && $addon->detect_req_type() !== EE_System::req_type_normal) { |
|
306 | + EED_Core_Rest_Api::invalidate_cached_route_data(); |
|
307 | + } |
|
308 | + } |
|
309 | + } |
|
310 | + |
|
311 | + |
|
312 | + |
|
313 | + /** |
|
314 | + * Removes the cached route data so it will get refreshed next time the WP API is used |
|
315 | + */ |
|
316 | + public static function invalidate_cached_route_data() |
|
317 | + { |
|
318 | + //delete the saved EE REST API routes |
|
319 | + foreach (EED_Core_Rest_Api::versions_served() as $version => $hidden) { |
|
320 | + delete_option(EED_Core_Rest_Api::saved_routes_option_names . $version); |
|
321 | + } |
|
322 | + } |
|
323 | + |
|
324 | + |
|
325 | + |
|
326 | + /** |
|
327 | + * Gets the EE route data |
|
328 | + * |
|
329 | + * @return array top-level key is the namespace, next-level key is the route and its value is array{ |
|
330 | + * @throws \EE_Error |
|
331 | + * @type string|array $callback |
|
332 | + * @type string $methods |
|
333 | + * @type boolean $hidden_endpoint |
|
334 | + * } |
|
335 | + */ |
|
336 | + public static function get_ee_route_data() |
|
337 | + { |
|
338 | + $ee_routes = array(); |
|
339 | + foreach (self::versions_served() as $version => $hidden_endpoints) { |
|
340 | + $ee_routes[self::ee_api_namespace . $version] = self::_get_ee_route_data_for_version( |
|
341 | + $version, |
|
342 | + $hidden_endpoints |
|
343 | + ); |
|
344 | + } |
|
345 | + return $ee_routes; |
|
346 | + } |
|
347 | + |
|
348 | + |
|
349 | + |
|
350 | + /** |
|
351 | + * Gets the EE route data from the wp options if it exists already, |
|
352 | + * otherwise re-generates it and saves it to the option |
|
353 | + * |
|
354 | + * @param string $version |
|
355 | + * @param boolean $hidden_endpoints |
|
356 | + * @return array |
|
357 | + * @throws \EE_Error |
|
358 | + */ |
|
359 | + protected static function _get_ee_route_data_for_version($version, $hidden_endpoints = false) |
|
360 | + { |
|
361 | + $ee_routes = get_option(self::saved_routes_option_names . $version, null); |
|
362 | + if (! $ee_routes || (defined('EE_REST_API_DEBUG_MODE') && EE_REST_API_DEBUG_MODE)) { |
|
363 | + $ee_routes = self::_save_ee_route_data_for_version($version, $hidden_endpoints); |
|
364 | + } |
|
365 | + return $ee_routes; |
|
366 | + } |
|
367 | + |
|
368 | + |
|
369 | + |
|
370 | + /** |
|
371 | + * Saves the EE REST API route data to a wp option and returns it |
|
372 | + * |
|
373 | + * @param string $version |
|
374 | + * @param boolean $hidden_endpoints |
|
375 | + * @return mixed|null |
|
376 | + * @throws \EE_Error |
|
377 | + */ |
|
378 | + protected static function _save_ee_route_data_for_version($version, $hidden_endpoints = false) |
|
379 | + { |
|
380 | + $instance = self::instance(); |
|
381 | + $routes = apply_filters( |
|
382 | + 'EED_Core_Rest_Api__save_ee_route_data_for_version__routes', |
|
383 | + array_replace_recursive( |
|
384 | + $instance->_get_config_route_data_for_version($version, $hidden_endpoints), |
|
385 | + $instance->_get_meta_route_data_for_version($version, $hidden_endpoints), |
|
386 | + $instance->_get_model_route_data_for_version($version, $hidden_endpoints), |
|
387 | + $instance->_get_rpc_route_data_for_version($version, $hidden_endpoints) |
|
388 | + ) |
|
389 | + ); |
|
390 | + $option_name = self::saved_routes_option_names . $version; |
|
391 | + if (get_option($option_name)) { |
|
392 | + update_option($option_name, $routes, true); |
|
393 | + } else { |
|
394 | + add_option($option_name, $routes, null, 'no'); |
|
395 | + } |
|
396 | + return $routes; |
|
397 | + } |
|
398 | + |
|
399 | + |
|
400 | + |
|
401 | + /** |
|
402 | + * Calculates all the EE routes and saves it to a WordPress option so we don't |
|
403 | + * need to calculate it on every request |
|
404 | + * |
|
405 | + * @deprecated since version 4.9.1 |
|
406 | + * @return void |
|
407 | + */ |
|
408 | + public static function save_ee_routes() |
|
409 | + { |
|
410 | + if (EE_Maintenance_Mode::instance()->models_can_query()) { |
|
411 | + $instance = self::instance(); |
|
412 | + $routes = apply_filters( |
|
413 | + 'EED_Core_Rest_Api__save_ee_routes__routes', |
|
414 | + array_replace_recursive( |
|
415 | + $instance->_register_config_routes(), |
|
416 | + $instance->_register_meta_routes(), |
|
417 | + $instance->_register_model_routes(), |
|
418 | + $instance->_register_rpc_routes() |
|
419 | + ) |
|
420 | + ); |
|
421 | + update_option(self::saved_routes_option_names, $routes, true); |
|
422 | + } |
|
423 | + } |
|
424 | + |
|
425 | + |
|
426 | + |
|
427 | + /** |
|
428 | + * Gets all the route information relating to EE models |
|
429 | + * |
|
430 | + * @return array @see get_ee_route_data |
|
431 | + * @deprecated since version 4.9.1 |
|
432 | + */ |
|
433 | + protected function _register_model_routes() |
|
434 | + { |
|
435 | + $model_routes = array(); |
|
436 | + foreach (self::versions_served() as $version => $hidden_endpoint) { |
|
437 | + $model_routes[EED_Core_Rest_Api::ee_api_namespace |
|
438 | + . $version] = $this->_get_config_route_data_for_version($version, $hidden_endpoint); |
|
439 | + } |
|
440 | + return $model_routes; |
|
441 | + } |
|
442 | + |
|
443 | + |
|
444 | + |
|
445 | + /** |
|
446 | + * Decides whether or not to add write endpoints for this model. |
|
447 | + * |
|
448 | + * Currently, this defaults to exclude all global tables and models |
|
449 | + * which would allow inserting WP core data (we don't want to duplicate |
|
450 | + * what WP API does, as it's unnecessary, extra work, and potentially extra bugs) |
|
451 | + * @param EEM_Base $model |
|
452 | + * @return bool |
|
453 | + */ |
|
454 | + public static function should_have_write_endpoints(EEM_Base $model) |
|
455 | + { |
|
456 | + if ($model->is_wp_core_model()){ |
|
457 | + return false; |
|
458 | + } |
|
459 | + foreach($model->get_tables() as $table){ |
|
460 | + if( $table->is_global()){ |
|
461 | + return false; |
|
462 | + } |
|
463 | + } |
|
464 | + return true; |
|
465 | + } |
|
466 | + |
|
467 | + |
|
468 | + |
|
469 | + /** |
|
470 | + * Gets the names of all models which should have plural routes (eg `ee/v4.8.36/events`) |
|
471 | + * in this versioned namespace of EE4 |
|
472 | + * @param $version |
|
473 | + * @return array keys are model names (eg 'Event') and values ar either classnames (eg 'EEM_Event') |
|
474 | + */ |
|
475 | + public static function model_names_with_plural_routes($version){ |
|
476 | + $model_version_info = new ModelVersionInfo($version); |
|
477 | + $models_to_register = $model_version_info->modelsForRequestedVersion(); |
|
478 | + //let's not bother having endpoints for extra metas |
|
479 | + unset( |
|
480 | + $models_to_register['Extra_Meta'], |
|
481 | + $models_to_register['Extra_Join'], |
|
482 | + $models_to_register['Post_Meta'] |
|
483 | + ); |
|
484 | + return apply_filters( |
|
485 | + 'FHEE__EED_Core_REST_API___register_model_routes', |
|
486 | + $models_to_register |
|
487 | + ); |
|
488 | + } |
|
489 | + |
|
490 | + |
|
491 | + |
|
492 | + /** |
|
493 | + * Gets the route data for EE models in the specified version |
|
494 | + * |
|
495 | + * @param string $version |
|
496 | + * @param boolean $hidden_endpoint |
|
497 | + * @return array |
|
498 | + * @throws EE_Error |
|
499 | + */ |
|
500 | + protected function _get_model_route_data_for_version($version, $hidden_endpoint = false) |
|
501 | + { |
|
502 | + $model_routes = array(); |
|
503 | + $model_version_info = new ModelVersionInfo($version); |
|
504 | + foreach (EED_Core_Rest_Api::model_names_with_plural_routes($version) as $model_name => $model_classname) { |
|
505 | + $model = \EE_Registry::instance()->load_model($model_name); |
|
506 | + //if this isn't a valid model then let's skip iterate to the next item in the loop. |
|
507 | + if (! $model instanceof EEM_Base) { |
|
508 | + continue; |
|
509 | + } |
|
510 | + //yes we could just register one route for ALL models, but then they wouldn't show up in the index |
|
511 | + $plural_model_route = EED_Core_Rest_Api::get_collection_route($model); |
|
512 | + $singular_model_route = EED_Core_Rest_Api::get_entity_route($model, '(?P<id>[^\/]+)'); |
|
513 | + $model_routes[$plural_model_route] = array( |
|
514 | + array( |
|
515 | + 'callback' => array( |
|
516 | + 'EventEspresso\core\libraries\rest_api\controllers\model\Read', |
|
517 | + 'handleRequestGetAll', |
|
518 | + ), |
|
519 | + 'callback_args' => array($version, $model_name), |
|
520 | + 'methods' => WP_REST_Server::READABLE, |
|
521 | + 'hidden_endpoint' => $hidden_endpoint, |
|
522 | + 'args' => $this->_get_read_query_params($model, $version), |
|
523 | + '_links' => array( |
|
524 | + 'self' => rest_url(EED_Core_Rest_Api::ee_api_namespace . $version . $singular_model_route), |
|
525 | + ), |
|
526 | + ), |
|
527 | + 'schema' => array( |
|
528 | + 'schema_callback' => array( |
|
529 | + 'EventEspresso\core\libraries\rest_api\controllers\model\Read', |
|
530 | + 'handleSchemaRequest', |
|
531 | + ), |
|
532 | + 'callback_args' => array($version, $model_name), |
|
533 | + ), |
|
534 | + ); |
|
535 | + $model_routes[$singular_model_route] = array( |
|
536 | + array( |
|
537 | + 'callback' => array( |
|
538 | + 'EventEspresso\core\libraries\rest_api\controllers\model\Read', |
|
539 | + 'handleRequestGetOne', |
|
540 | + ), |
|
541 | + 'callback_args' => array($version, $model_name), |
|
542 | + 'methods' => WP_REST_Server::READABLE, |
|
543 | + 'hidden_endpoint' => $hidden_endpoint, |
|
544 | + 'args' => $this->_get_response_selection_query_params($model, $version), |
|
545 | + ), |
|
546 | + ); |
|
547 | + if( apply_filters( |
|
548 | + 'FHEE__EED_Core_Rest_Api___get_model_route_data_for_version__add_write_endpoints', |
|
549 | + EED_Core_Rest_Api::should_have_write_endpoints($model), |
|
550 | + $model |
|
551 | + )){ |
|
552 | + $model_routes[$plural_model_route][] = array( |
|
553 | + 'callback' => array( |
|
554 | + 'EventEspresso\core\libraries\rest_api\controllers\model\Write', |
|
555 | + 'handleRequestInsert', |
|
556 | + ), |
|
557 | + 'callback_args' => array($version, $model_name), |
|
558 | + 'methods' => WP_REST_Server::CREATABLE, |
|
559 | + 'hidden_endpoint' => $hidden_endpoint, |
|
560 | + 'args' => $this->_get_write_params($model_name, $model_version_info, true), |
|
561 | + ); |
|
562 | + $model_routes[$singular_model_route] = array_merge( |
|
563 | + $model_routes[$singular_model_route], |
|
564 | + array( |
|
565 | + array( |
|
566 | + 'callback' => array( |
|
567 | + 'EventEspresso\core\libraries\rest_api\controllers\model\Write', |
|
568 | + 'handleRequestUpdate', |
|
569 | + ), |
|
570 | + 'callback_args' => array($version, $model_name), |
|
571 | + 'methods' => WP_REST_Server::EDITABLE, |
|
572 | + 'hidden_endpoint' => $hidden_endpoint, |
|
573 | + 'args' => $this->_get_write_params($model_name, $model_version_info), |
|
574 | + ), |
|
575 | + array( |
|
576 | + 'callback' => array( |
|
577 | + 'EventEspresso\core\libraries\rest_api\controllers\model\Write', |
|
578 | + 'handleRequestDelete', |
|
579 | + ), |
|
580 | + 'callback_args' => array($version, $model_name), |
|
581 | + 'methods' => WP_REST_Server::DELETABLE, |
|
582 | + 'hidden_endpoint' => $hidden_endpoint, |
|
583 | + 'args' => $this->_get_delete_query_params($model, $version), |
|
584 | + ) |
|
585 | + ) |
|
586 | + ); |
|
587 | + } |
|
588 | + foreach ($model->relation_settings() as $relation_name => $relation_obj) { |
|
589 | + |
|
590 | + $related_route = EED_Core_Rest_Api::get_relation_route_via( |
|
591 | + $model, |
|
592 | + '(?P<id>[^\/]+)', |
|
593 | + $relation_obj |
|
594 | + ); |
|
595 | + $endpoints = array( |
|
596 | + array( |
|
597 | + 'callback' => array( |
|
598 | + 'EventEspresso\core\libraries\rest_api\controllers\model\Read', |
|
599 | + 'handleRequestGetRelated', |
|
600 | + ), |
|
601 | + 'callback_args' => array($version, $model_name, $relation_name), |
|
602 | + 'methods' => WP_REST_Server::READABLE, |
|
603 | + 'hidden_endpoint' => $hidden_endpoint, |
|
604 | + 'args' => $this->_get_read_query_params($relation_obj->get_other_model(), $version), |
|
605 | + ), |
|
606 | + ); |
|
607 | + $model_routes[$related_route] = $endpoints; |
|
608 | + } |
|
609 | + } |
|
610 | + return $model_routes; |
|
611 | + } |
|
612 | + |
|
613 | + |
|
614 | + |
|
615 | + /** |
|
616 | + * Gets the relative URI to a model's REST API plural route, after the EE4 versioned namespace, |
|
617 | + * excluding the preceding slash. |
|
618 | + * Eg you pass get_plural_route_to('Event') = 'events' |
|
619 | + * |
|
620 | + * @param EEM_Base $model |
|
621 | + * @return string |
|
622 | + */ |
|
623 | + public static function get_collection_route(EEM_Base $model) |
|
624 | + { |
|
625 | + return EEH_Inflector::pluralize_and_lower($model->get_this_model_name()); |
|
626 | + } |
|
627 | + |
|
628 | + |
|
629 | + |
|
630 | + /** |
|
631 | + * Gets the relative URI to a model's REST API singular route, after the EE4 versioned namespace, |
|
632 | + * excluding the preceding slash. |
|
633 | + * Eg you pass get_plural_route_to('Event', 12) = 'events/12' |
|
634 | + * |
|
635 | + * @param EEM_Base $model eg Event or Venue |
|
636 | + * @param string $id |
|
637 | + * @return string |
|
638 | + */ |
|
639 | + public static function get_entity_route($model, $id) |
|
640 | + { |
|
641 | + return EED_Core_Rest_Api::get_collection_route($model). '/' . $id; |
|
642 | + } |
|
643 | + |
|
644 | + |
|
645 | + /** |
|
646 | + * Gets the relative URI to a model's REST API singular route, after the EE4 versioned namespace, |
|
647 | + * excluding the preceding slash. |
|
648 | + * Eg you pass get_plural_route_to('Event', 12) = 'events/12' |
|
649 | + * |
|
650 | + * @param EEM_Base $model eg Event or Venue |
|
651 | + * @param string $id |
|
652 | + * @param EE_Model_Relation_Base $relation_obj |
|
653 | + * @return string |
|
654 | + */ |
|
655 | + public static function get_relation_route_via(EEM_Base $model, $id, EE_Model_Relation_Base $relation_obj) |
|
656 | + { |
|
657 | + $related_model_name_endpoint_part = ModelRead::getRelatedEntityName( |
|
658 | + $relation_obj->get_other_model()->get_this_model_name(), |
|
659 | + $relation_obj |
|
660 | + ); |
|
661 | + return EED_Core_Rest_Api::get_entity_route($model, $id) . '/' . $related_model_name_endpoint_part; |
|
662 | + } |
|
663 | + |
|
664 | + |
|
665 | + |
|
666 | + /** |
|
667 | + * Adds onto the $relative_route the EE4 REST API versioned namespace. |
|
668 | + * Eg if given '4.8.36' and 'events', will return 'ee/v4.8.36/events' |
|
669 | + * @param string $relative_route |
|
670 | + * @param string $version |
|
671 | + * @return string |
|
672 | + */ |
|
673 | + public static function get_versioned_route_to($relative_route, $version = '4.8.36'){ |
|
674 | + return '/' . EED_Core_Rest_Api::ee_api_namespace . $version . '/' . $relative_route; |
|
675 | + } |
|
676 | + |
|
677 | + |
|
678 | + |
|
679 | + /** |
|
680 | + * Adds all the RPC-style routes (remote procedure call-like routes, ie |
|
681 | + * routes that don't conform to the traditional REST CRUD-style). |
|
682 | + * |
|
683 | + * @deprecated since 4.9.1 |
|
684 | + */ |
|
685 | + protected function _register_rpc_routes() |
|
686 | + { |
|
687 | + $routes = array(); |
|
688 | + foreach (self::versions_served() as $version => $hidden_endpoint) { |
|
689 | + $routes[self::ee_api_namespace . $version] = $this->_get_rpc_route_data_for_version( |
|
690 | + $version, |
|
691 | + $hidden_endpoint |
|
692 | + ); |
|
693 | + } |
|
694 | + return $routes; |
|
695 | + } |
|
696 | + |
|
697 | + |
|
698 | + |
|
699 | + /** |
|
700 | + * @param string $version |
|
701 | + * @param boolean $hidden_endpoint |
|
702 | + * @return array |
|
703 | + */ |
|
704 | + protected function _get_rpc_route_data_for_version($version, $hidden_endpoint = false) |
|
705 | + { |
|
706 | + $this_versions_routes = array(); |
|
707 | + //checkin endpoint |
|
708 | + $this_versions_routes['registrations/(?P<REG_ID>\d+)/toggle_checkin_for_datetime/(?P<DTT_ID>\d+)'] = array( |
|
709 | + array( |
|
710 | + 'callback' => array( |
|
711 | + 'EventEspresso\core\libraries\rest_api\controllers\rpc\Checkin', |
|
712 | + 'handleRequestToggleCheckin', |
|
713 | + ), |
|
714 | + 'methods' => WP_REST_Server::CREATABLE, |
|
715 | + 'hidden_endpoint' => $hidden_endpoint, |
|
716 | + 'args' => array( |
|
717 | + 'force' => array( |
|
718 | + 'required' => false, |
|
719 | + 'default' => false, |
|
720 | + 'description' => __( |
|
721 | + // @codingStandardsIgnoreStart |
|
722 | + 'Whether to force toggle checkin, or to verify the registration status and allowed ticket uses', |
|
723 | + // @codingStandardsIgnoreEnd |
|
724 | + 'event_espresso' |
|
725 | + ), |
|
726 | + ), |
|
727 | + ), |
|
728 | + 'callback_args' => array($version), |
|
729 | + ), |
|
730 | + ); |
|
731 | + return apply_filters( |
|
732 | + 'FHEE__EED_Core_Rest_Api___register_rpc_routes__this_versions_routes', |
|
733 | + $this_versions_routes, |
|
734 | + $version, |
|
735 | + $hidden_endpoint |
|
736 | + ); |
|
737 | + } |
|
738 | + |
|
739 | + |
|
740 | + |
|
741 | + /** |
|
742 | + * Gets the query params that can be used when request one or many |
|
743 | + * |
|
744 | + * @param EEM_Base $model |
|
745 | + * @param string $version |
|
746 | + * @return array |
|
747 | + */ |
|
748 | + protected function _get_response_selection_query_params(\EEM_Base $model, $version) |
|
749 | + { |
|
750 | + return apply_filters( |
|
751 | + 'FHEE__EED_Core_Rest_Api___get_response_selection_query_params', |
|
752 | + array( |
|
753 | + 'include' => array( |
|
754 | + 'required' => false, |
|
755 | + 'default' => '*', |
|
756 | + 'type' => 'string', |
|
757 | + ), |
|
758 | + 'calculate' => array( |
|
759 | + 'required' => false, |
|
760 | + 'default' => '', |
|
761 | + 'enum' => self::$_field_calculator->retrieveCalculatedFieldsForModel($model), |
|
762 | + 'type' => 'string', |
|
763 | + //because we accept a CSV'd list of the enumerated strings, WP core validation and sanitization |
|
764 | + //freaks out. We'll just validate this argument while handling the request |
|
765 | + 'validate_callback' => null, |
|
766 | + 'sanitize_callback' => null, |
|
767 | + ), |
|
768 | + ), |
|
769 | + $model, |
|
770 | + $version |
|
771 | + ); |
|
772 | + } |
|
773 | + |
|
774 | + |
|
775 | + |
|
776 | + /** |
|
777 | + * Gets the parameters acceptable for delete requests |
|
778 | + * |
|
779 | + * @param \EEM_Base $model |
|
780 | + * @param string $version |
|
781 | + * @return array |
|
782 | + */ |
|
783 | + protected function _get_delete_query_params(\EEM_Base $model, $version) |
|
784 | + { |
|
785 | + $params_for_delete = array( |
|
786 | + 'allow_blocking' => array( |
|
787 | + 'required' => false, |
|
788 | + 'default' => true, |
|
789 | + 'type' => 'boolean', |
|
790 | + ), |
|
791 | + ); |
|
792 | + $params_for_delete['force'] = array( |
|
793 | + 'required' => false, |
|
794 | + 'default' => false, |
|
795 | + 'type' => 'boolean', |
|
796 | + ); |
|
797 | + return apply_filters( |
|
798 | + 'FHEE__EED_Core_Rest_Api___get_delete_query_params', |
|
799 | + $params_for_delete, |
|
800 | + $model, |
|
801 | + $version |
|
802 | + ); |
|
803 | + } |
|
804 | + |
|
805 | + |
|
806 | + |
|
807 | + /** |
|
808 | + * Gets info about reading query params that are acceptable |
|
809 | + * |
|
810 | + * @param \EEM_Base $model eg 'Event' or 'Venue' |
|
811 | + * @param string $version |
|
812 | + * @return array describing the args acceptable when querying this model |
|
813 | + * @throws EE_Error |
|
814 | + */ |
|
815 | + protected function _get_read_query_params(\EEM_Base $model, $version) |
|
816 | + { |
|
817 | + $default_orderby = array(); |
|
818 | + foreach ($model->get_combined_primary_key_fields() as $key_field) { |
|
819 | + $default_orderby[$key_field->get_name()] = 'ASC'; |
|
820 | + } |
|
821 | + return array_merge( |
|
822 | + $this->_get_response_selection_query_params($model, $version), |
|
823 | + array( |
|
824 | + 'where' => array( |
|
825 | + 'required' => false, |
|
826 | + 'default' => array(), |
|
827 | + 'type' => 'object', |
|
828 | + //because we accept an almost infinite list of possible where conditions, WP |
|
829 | + // core validation and sanitization freaks out. We'll just validate this argument |
|
830 | + // while handling the request |
|
831 | + 'validate_callback' => null, |
|
832 | + 'sanitize_callback' => null, |
|
833 | + ), |
|
834 | + 'limit' => array( |
|
835 | + 'required' => false, |
|
836 | + 'default' => EED_Core_Rest_Api::get_default_query_limit(), |
|
837 | + 'type' => array( |
|
838 | + 'array', |
|
839 | + 'string', |
|
840 | + 'integer', |
|
841 | + ), |
|
842 | + //because we accept a variety of types, WP core validation and sanitization |
|
843 | + //freaks out. We'll just validate this argument while handling the request |
|
844 | + 'validate_callback' => null, |
|
845 | + 'sanitize_callback' => null, |
|
846 | + ), |
|
847 | + 'order_by' => array( |
|
848 | + 'required' => false, |
|
849 | + 'default' => $default_orderby, |
|
850 | + 'type' => array( |
|
851 | + 'object', |
|
852 | + 'string', |
|
853 | + ),//because we accept a variety of types, WP core validation and sanitization |
|
854 | + //freaks out. We'll just validate this argument while handling the request |
|
855 | + 'validate_callback' => null, |
|
856 | + 'sanitize_callback' => null, |
|
857 | + ), |
|
858 | + 'group_by' => array( |
|
859 | + 'required' => false, |
|
860 | + 'default' => null, |
|
861 | + 'type' => array( |
|
862 | + 'object', |
|
863 | + 'string', |
|
864 | + ), |
|
865 | + //because we accept an almost infinite list of possible groupings, |
|
866 | + // WP core validation and sanitization |
|
867 | + //freaks out. We'll just validate this argument while handling the request |
|
868 | + 'validate_callback' => null, |
|
869 | + 'sanitize_callback' => null, |
|
870 | + ), |
|
871 | + 'having' => array( |
|
872 | + 'required' => false, |
|
873 | + 'default' => null, |
|
874 | + 'type' => 'object', |
|
875 | + //because we accept an almost infinite list of possible where conditions, WP |
|
876 | + // core validation and sanitization freaks out. We'll just validate this argument |
|
877 | + // while handling the request |
|
878 | + 'validate_callback' => null, |
|
879 | + 'sanitize_callback' => null, |
|
880 | + ), |
|
881 | + 'caps' => array( |
|
882 | + 'required' => false, |
|
883 | + 'default' => EEM_Base::caps_read, |
|
884 | + 'type' => 'string', |
|
885 | + 'enum' => array( |
|
886 | + EEM_Base::caps_read, |
|
887 | + EEM_Base::caps_read_admin, |
|
888 | + EEM_Base::caps_edit, |
|
889 | + EEM_Base::caps_delete |
|
890 | + ) |
|
891 | + ), |
|
892 | + ) |
|
893 | + ); |
|
894 | + } |
|
895 | + |
|
896 | + |
|
897 | + |
|
898 | + /** |
|
899 | + * Gets parameter information for a model regarding writing data |
|
900 | + * |
|
901 | + * @param string $model_name |
|
902 | + * @param ModelVersionInfo $model_version_info |
|
903 | + * @param boolean $create whether this is for request to create (in which case we need |
|
904 | + * all required params) or just to update (in which case we don't need those on every request) |
|
905 | + * @return array |
|
906 | + */ |
|
907 | + protected function _get_write_params( |
|
908 | + $model_name, |
|
909 | + ModelVersionInfo $model_version_info, |
|
910 | + $create = false |
|
911 | + ) { |
|
912 | + $model = EE_Registry::instance()->load_model($model_name); |
|
913 | + $fields = $model_version_info->fieldsOnModelInThisVersion($model); |
|
914 | + $args_info = array(); |
|
915 | + foreach ($fields as $field_name => $field_obj) { |
|
916 | + if ($field_obj->is_auto_increment()) { |
|
917 | + //totally ignore auto increment IDs |
|
918 | + continue; |
|
919 | + } |
|
920 | + $arg_info = $field_obj->getSchema(); |
|
921 | + $required = $create && ! $field_obj->is_nullable() && $field_obj->get_default_value() === null; |
|
922 | + $arg_info['required'] = $required; |
|
923 | + //remove the read-only flag. If it were read-only we wouldn't list it as an argument while writing, right? |
|
924 | + unset($arg_info['readonly']); |
|
925 | + $schema_properties = $field_obj->getSchemaProperties(); |
|
926 | + if ( |
|
927 | + isset($schema_properties['raw']) |
|
928 | + && $field_obj->getSchemaType() === 'object' |
|
929 | + ) { |
|
930 | + //if there's a "raw" form of this argument, use those properties instead |
|
931 | + $arg_info = array_replace( |
|
932 | + $arg_info, |
|
933 | + $schema_properties['raw'] |
|
934 | + ); |
|
935 | + } |
|
936 | + $arg_info['default'] = ModelDataTranslator::prepareFieldValueForJson( |
|
937 | + $field_obj, |
|
938 | + $field_obj->get_default_value(), |
|
939 | + $model_version_info->requestedVersion() |
|
940 | + ); |
|
941 | + //we do our own validation and sanitization within the controller |
|
942 | + $arg_info['sanitize_callback'] = |
|
943 | + array( |
|
944 | + 'EED_Core_Rest_Api', |
|
945 | + 'default_sanitize_callback', |
|
946 | + ); |
|
947 | + $args_info[$field_name] = $arg_info; |
|
948 | + if ($field_obj instanceof EE_Datetime_Field) { |
|
949 | + $gmt_arg_info = $arg_info; |
|
950 | + $gmt_arg_info['description'] = sprintf( |
|
951 | + esc_html__( |
|
952 | + '%1$s - the value for this field in UTC. Ignored if %2$s is provided.', |
|
953 | + 'event_espresso' |
|
954 | + ), |
|
955 | + $field_obj->get_nicename(), |
|
956 | + $field_name |
|
957 | + ); |
|
958 | + $args_info[$field_name . '_gmt'] = $gmt_arg_info; |
|
959 | + } |
|
960 | + } |
|
961 | + return $args_info; |
|
962 | + } |
|
963 | + |
|
964 | + |
|
965 | + |
|
966 | + /** |
|
967 | + * Replacement for WP API's 'rest_parse_request_arg'. |
|
968 | + * If the value is blank but not required, don't bother validating it. |
|
969 | + * Also, it uses our email validation instead of WP API's default. |
|
970 | + * |
|
971 | + * @param $value |
|
972 | + * @param WP_REST_Request $request |
|
973 | + * @param $param |
|
974 | + * @return bool|true|WP_Error |
|
975 | + * @throws InvalidArgumentException |
|
976 | + * @throws InvalidInterfaceException |
|
977 | + * @throws InvalidDataTypeException |
|
978 | + */ |
|
979 | + public static function default_sanitize_callback( $value, WP_REST_Request $request, $param) |
|
980 | + { |
|
981 | + $attributes = $request->get_attributes(); |
|
982 | + if (! isset($attributes['args'][$param]) |
|
983 | + || ! is_array($attributes['args'][$param])) { |
|
984 | + $validation_result = true; |
|
985 | + } else { |
|
986 | + $args = $attributes['args'][$param]; |
|
987 | + if (( |
|
988 | + $value === '' |
|
989 | + || $value === null |
|
990 | + ) |
|
991 | + && (! isset($args['required']) |
|
992 | + || $args['required'] === false |
|
993 | + ) |
|
994 | + ) { |
|
995 | + //not required and not provided? that's cool |
|
996 | + $validation_result = true; |
|
997 | + } elseif (isset($args['format']) |
|
998 | + && $args['format'] === 'email' |
|
999 | + ) { |
|
1000 | + $validation_result = true; |
|
1001 | + if (! self::_validate_email($value)) { |
|
1002 | + $validation_result = new WP_Error( |
|
1003 | + 'rest_invalid_param', |
|
1004 | + esc_html__( |
|
1005 | + 'The email address is not valid or does not exist.', |
|
1006 | + 'event_espresso' |
|
1007 | + ) |
|
1008 | + ); |
|
1009 | + } |
|
1010 | + } else { |
|
1011 | + $validation_result = rest_validate_value_from_schema($value, $args, $param); |
|
1012 | + } |
|
1013 | + } |
|
1014 | + if (is_wp_error($validation_result)) { |
|
1015 | + return $validation_result; |
|
1016 | + } |
|
1017 | + return rest_sanitize_request_arg($value, $request, $param); |
|
1018 | + } |
|
1019 | + |
|
1020 | + |
|
1021 | + |
|
1022 | + /** |
|
1023 | + * Returns whether or not this email address is valid. Copied from EE_Email_Validation_Strategy::_validate_email() |
|
1024 | + * |
|
1025 | + * @param $email |
|
1026 | + * @return bool |
|
1027 | + * @throws InvalidArgumentException |
|
1028 | + * @throws InvalidInterfaceException |
|
1029 | + * @throws InvalidDataTypeException |
|
1030 | + */ |
|
1031 | + protected static function _validate_email($email){ |
|
1032 | + try { |
|
1033 | + EmailAddressFactory::create($email); |
|
1034 | + return true; |
|
1035 | + } catch (EmailValidationException $e) { |
|
1036 | + return false; |
|
1037 | + } |
|
1038 | + } |
|
1039 | + |
|
1040 | + |
|
1041 | + |
|
1042 | + /** |
|
1043 | + * Gets routes for the config |
|
1044 | + * |
|
1045 | + * @return array @see _register_model_routes |
|
1046 | + * @deprecated since version 4.9.1 |
|
1047 | + */ |
|
1048 | + protected function _register_config_routes() |
|
1049 | + { |
|
1050 | + $config_routes = array(); |
|
1051 | + foreach (self::versions_served() as $version => $hidden_endpoint) { |
|
1052 | + $config_routes[self::ee_api_namespace . $version] = $this->_get_config_route_data_for_version( |
|
1053 | + $version, |
|
1054 | + $hidden_endpoint |
|
1055 | + ); |
|
1056 | + } |
|
1057 | + return $config_routes; |
|
1058 | + } |
|
1059 | + |
|
1060 | + |
|
1061 | + |
|
1062 | + /** |
|
1063 | + * Gets routes for the config for the specified version |
|
1064 | + * |
|
1065 | + * @param string $version |
|
1066 | + * @param boolean $hidden_endpoint |
|
1067 | + * @return array |
|
1068 | + */ |
|
1069 | + protected function _get_config_route_data_for_version($version, $hidden_endpoint) |
|
1070 | + { |
|
1071 | + return array( |
|
1072 | + 'config' => array( |
|
1073 | + array( |
|
1074 | + 'callback' => array( |
|
1075 | + 'EventEspresso\core\libraries\rest_api\controllers\config\Read', |
|
1076 | + 'handleRequest', |
|
1077 | + ), |
|
1078 | + 'methods' => WP_REST_Server::READABLE, |
|
1079 | + 'hidden_endpoint' => $hidden_endpoint, |
|
1080 | + 'callback_args' => array($version), |
|
1081 | + ), |
|
1082 | + ), |
|
1083 | + 'site_info' => array( |
|
1084 | + array( |
|
1085 | + 'callback' => array( |
|
1086 | + 'EventEspresso\core\libraries\rest_api\controllers\config\Read', |
|
1087 | + 'handleRequestSiteInfo', |
|
1088 | + ), |
|
1089 | + 'methods' => WP_REST_Server::READABLE, |
|
1090 | + 'hidden_endpoint' => $hidden_endpoint, |
|
1091 | + 'callback_args' => array($version), |
|
1092 | + ), |
|
1093 | + ), |
|
1094 | + ); |
|
1095 | + } |
|
1096 | + |
|
1097 | + |
|
1098 | + |
|
1099 | + /** |
|
1100 | + * Gets the meta info routes |
|
1101 | + * |
|
1102 | + * @return array @see _register_model_routes |
|
1103 | + * @deprecated since version 4.9.1 |
|
1104 | + */ |
|
1105 | + protected function _register_meta_routes() |
|
1106 | + { |
|
1107 | + $meta_routes = array(); |
|
1108 | + foreach (self::versions_served() as $version => $hidden_endpoint) { |
|
1109 | + $meta_routes[self::ee_api_namespace . $version] = $this->_get_meta_route_data_for_version( |
|
1110 | + $version, |
|
1111 | + $hidden_endpoint |
|
1112 | + ); |
|
1113 | + } |
|
1114 | + return $meta_routes; |
|
1115 | + } |
|
1116 | + |
|
1117 | + |
|
1118 | + |
|
1119 | + /** |
|
1120 | + * @param string $version |
|
1121 | + * @param boolean $hidden_endpoint |
|
1122 | + * @return array |
|
1123 | + */ |
|
1124 | + protected function _get_meta_route_data_for_version($version, $hidden_endpoint = false) |
|
1125 | + { |
|
1126 | + return array( |
|
1127 | + 'resources' => array( |
|
1128 | + array( |
|
1129 | + 'callback' => array( |
|
1130 | + 'EventEspresso\core\libraries\rest_api\controllers\model\Meta', |
|
1131 | + 'handleRequestModelsMeta', |
|
1132 | + ), |
|
1133 | + 'methods' => WP_REST_Server::READABLE, |
|
1134 | + 'hidden_endpoint' => $hidden_endpoint, |
|
1135 | + 'callback_args' => array($version), |
|
1136 | + ), |
|
1137 | + ), |
|
1138 | + ); |
|
1139 | + } |
|
1140 | + |
|
1141 | + |
|
1142 | + |
|
1143 | + /** |
|
1144 | + * Tries to hide old 4.6 endpoints from the |
|
1145 | + * |
|
1146 | + * @param array $route_data |
|
1147 | + * @return array |
|
1148 | + * @throws \EE_Error |
|
1149 | + */ |
|
1150 | + public static function hide_old_endpoints($route_data) |
|
1151 | + { |
|
1152 | + //allow API clients to override which endpoints get hidden, in case |
|
1153 | + //they want to discover particular endpoints |
|
1154 | + //also, we don't have access to the request so we have to just grab it from the superglobal |
|
1155 | + $force_show_ee_namespace = ltrim( |
|
1156 | + EEH_Array::is_set($_REQUEST, 'force_show_ee_namespace', ''), |
|
1157 | + '/' |
|
1158 | + ); |
|
1159 | + foreach (EED_Core_Rest_Api::get_ee_route_data() as $namespace => $relative_urls) { |
|
1160 | + foreach ($relative_urls as $resource_name => $endpoints) { |
|
1161 | + foreach ($endpoints as $key => $endpoint) { |
|
1162 | + //skip schema and other route options |
|
1163 | + if (! is_numeric($key)) { |
|
1164 | + continue; |
|
1165 | + } |
|
1166 | + //by default, hide "hidden_endpoint"s, unless the request indicates |
|
1167 | + //to $force_show_ee_namespace, in which case only show that one |
|
1168 | + //namespace's endpoints (and hide all others) |
|
1169 | + if ( |
|
1170 | + ($force_show_ee_namespace !== '' && $force_show_ee_namespace !== $namespace) |
|
1171 | + || ($endpoint['hidden_endpoint'] && $force_show_ee_namespace === '') |
|
1172 | + ) { |
|
1173 | + $full_route = '/' . ltrim($namespace, '/'); |
|
1174 | + $full_route .= '/' . ltrim($resource_name, '/'); |
|
1175 | + unset($route_data[$full_route]); |
|
1176 | + } |
|
1177 | + } |
|
1178 | + } |
|
1179 | + } |
|
1180 | + return $route_data; |
|
1181 | + } |
|
1182 | + |
|
1183 | + |
|
1184 | + |
|
1185 | + /** |
|
1186 | + * Returns an array describing which versions of core support serving requests for. |
|
1187 | + * Keys are core versions' major and minor version, and values are the |
|
1188 | + * LOWEST requested version they can serve. Eg, 4.7 can serve requests for 4.6-like |
|
1189 | + * data by just removing a few models and fields from the responses. However, 4.15 might remove |
|
1190 | + * the answers table entirely, in which case it would be very difficult for |
|
1191 | + * it to serve 4.6-style responses. |
|
1192 | + * Versions of core that are missing from this array are unknowns. |
|
1193 | + * previous ver |
|
1194 | + * |
|
1195 | + * @return array |
|
1196 | + */ |
|
1197 | + public static function version_compatibilities() |
|
1198 | + { |
|
1199 | + return apply_filters( |
|
1200 | + 'FHEE__EED_Core_REST_API__version_compatibilities', |
|
1201 | + array( |
|
1202 | + '4.8.29' => '4.8.29', |
|
1203 | + '4.8.33' => '4.8.29', |
|
1204 | + '4.8.34' => '4.8.29', |
|
1205 | + '4.8.36' => '4.8.29', |
|
1206 | + ) |
|
1207 | + ); |
|
1208 | + } |
|
1209 | + |
|
1210 | + |
|
1211 | + |
|
1212 | + /** |
|
1213 | + * Gets the latest API version served. Eg if there |
|
1214 | + * are two versions served of the API, 4.8.29 and 4.8.32, and |
|
1215 | + * we are on core version 4.8.34, it will return the string "4.8.32" |
|
1216 | + * |
|
1217 | + * @return string |
|
1218 | + */ |
|
1219 | + public static function latest_rest_api_version() |
|
1220 | + { |
|
1221 | + $versions_served = \EED_Core_Rest_Api::versions_served(); |
|
1222 | + $versions_served_keys = array_keys($versions_served); |
|
1223 | + return end($versions_served_keys); |
|
1224 | + } |
|
1225 | + |
|
1226 | + |
|
1227 | + |
|
1228 | + /** |
|
1229 | + * Using EED_Core_Rest_Api::version_compatibilities(), determines what version of |
|
1230 | + * EE the API can serve requests for. Eg, if we are on 4.15 of core, and |
|
1231 | + * we can serve requests from 4.12 or later, this will return array( '4.12', '4.13', '4.14', '4.15' ). |
|
1232 | + * We also indicate whether or not this version should be put in the index or not |
|
1233 | + * |
|
1234 | + * @return array keys are API version numbers (just major and minor numbers), and values |
|
1235 | + * are whether or not they should be hidden |
|
1236 | + */ |
|
1237 | + public static function versions_served() |
|
1238 | + { |
|
1239 | + $versions_served = array(); |
|
1240 | + $possibly_served_versions = EED_Core_Rest_Api::version_compatibilities(); |
|
1241 | + $lowest_compatible_version = end($possibly_served_versions); |
|
1242 | + reset($possibly_served_versions); |
|
1243 | + $versions_served_historically = array_keys($possibly_served_versions); |
|
1244 | + $latest_version = end($versions_served_historically); |
|
1245 | + reset($versions_served_historically); |
|
1246 | + //for each version of core we have ever served: |
|
1247 | + foreach ($versions_served_historically as $key_versioned_endpoint) { |
|
1248 | + //if it's not above the current core version, and it's compatible with the current version of core |
|
1249 | + if ($key_versioned_endpoint === $latest_version) { |
|
1250 | + //don't hide the latest version in the index |
|
1251 | + $versions_served[$key_versioned_endpoint] = false; |
|
1252 | + } elseif ( |
|
1253 | + $key_versioned_endpoint >= $lowest_compatible_version |
|
1254 | + && $key_versioned_endpoint < EED_Core_Rest_Api::core_version() |
|
1255 | + ) { |
|
1256 | + //include, but hide, previous versions which are still supported |
|
1257 | + $versions_served[$key_versioned_endpoint] = true; |
|
1258 | + } elseif (apply_filters( |
|
1259 | + 'FHEE__EED_Core_Rest_Api__versions_served__include_incompatible_versions', |
|
1260 | + false, |
|
1261 | + $possibly_served_versions |
|
1262 | + )) { |
|
1263 | + //if a version is no longer supported, don't include it in index or list of versions served |
|
1264 | + $versions_served[$key_versioned_endpoint] = true; |
|
1265 | + } |
|
1266 | + } |
|
1267 | + return $versions_served; |
|
1268 | + } |
|
1269 | + |
|
1270 | + |
|
1271 | + |
|
1272 | + /** |
|
1273 | + * Gets the major and minor version of EE core's version string |
|
1274 | + * |
|
1275 | + * @return string |
|
1276 | + */ |
|
1277 | + public static function core_version() |
|
1278 | + { |
|
1279 | + return apply_filters( |
|
1280 | + 'FHEE__EED_Core_REST_API__core_version', |
|
1281 | + implode( |
|
1282 | + '.', |
|
1283 | + array_slice( |
|
1284 | + explode( |
|
1285 | + '.', |
|
1286 | + espresso_version() |
|
1287 | + ), |
|
1288 | + 0, |
|
1289 | + 3 |
|
1290 | + ) |
|
1291 | + ) |
|
1292 | + ); |
|
1293 | + } |
|
1294 | + |
|
1295 | + |
|
1296 | + |
|
1297 | + /** |
|
1298 | + * Gets the default limit that should be used when querying for resources |
|
1299 | + * |
|
1300 | + * @return int |
|
1301 | + */ |
|
1302 | + public static function get_default_query_limit() |
|
1303 | + { |
|
1304 | + //we actually don't use a const because we want folks to always use |
|
1305 | + //this method, not the const directly |
|
1306 | + return apply_filters( |
|
1307 | + 'FHEE__EED_Core_Rest_Api__get_default_query_limit', |
|
1308 | + 50 |
|
1309 | + ); |
|
1310 | + } |
|
1311 | + |
|
1312 | + |
|
1313 | + |
|
1314 | + /** |
|
1315 | + * run - initial module setup |
|
1316 | + * |
|
1317 | + * @access public |
|
1318 | + * @param WP $WP |
|
1319 | + * @return void |
|
1320 | + */ |
|
1321 | + public function run($WP) |
|
1322 | + { |
|
1323 | + } |
|
1324 | 1324 | } |
1325 | 1325 | |
1326 | 1326 | // End of file EED_Core_Rest_Api.module.php |