Completed
Branch BUG/11184/undefined-rest-api-v... (fe5e19)
by
unknown
78:03 queued 67:31
created
modules/core_rest_api/EED_Core_Rest_Api.module.php 1 patch
Indentation   +1296 added lines, -1296 removed lines patch added patch discarded remove patch
@@ -24,1303 +24,1303 @@
 block discarded – undo
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
Please login to merge, or discard this patch.