Completed
Branch BUG/rest-api-taxonomy-relation... (3c7ab3)
by
unknown
17:42 queued 09:04
created
core/libraries/rest_api/controllers/model/Read.php 2 patches
Indentation   +1577 added lines, -1577 removed lines patch added patch discarded remove patch
@@ -45,1581 +45,1581 @@
 block discarded – undo
45 45
 {
46 46
 
47 47
 
48
-    /**
49
-     * @var CalculatedModelFields
50
-     */
51
-    protected $fields_calculator;
52
-
53
-
54
-    /**
55
-     * Read constructor.
56
-     * @param CalculatedModelFields $fields_calculator
57
-     */
58
-    public function __construct(CalculatedModelFields $fields_calculator)
59
-    {
60
-        parent::__construct();
61
-        $this->fields_calculator = $fields_calculator;
62
-    }
63
-
64
-
65
-    /**
66
-     * Handles requests to get all (or a filtered subset) of entities for a particular model
67
-     *
68
-     * @param WP_REST_Request $request
69
-     * @param string $version
70
-     * @param string $model_name
71
-     * @return WP_REST_Response|WP_Error
72
-     * @throws InvalidArgumentException
73
-     * @throws InvalidDataTypeException
74
-     * @throws InvalidInterfaceException
75
-     */
76
-    public static function handleRequestGetAll(WP_REST_Request $request, $version, $model_name)
77
-    {
78
-        $controller = LoaderFactory::getLoader()->getNew('EventEspresso\core\libraries\rest_api\controllers\model\Read');
79
-        try {
80
-            $controller->setRequestedVersion($version);
81
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
82
-                return $controller->sendResponse(
83
-                    new WP_Error(
84
-                        'endpoint_parsing_error',
85
-                        sprintf(
86
-                            __(
87
-                                'There is no model for endpoint %s. Please contact event espresso support',
88
-                                'event_espresso'
89
-                            ),
90
-                            $model_name
91
-                        )
92
-                    )
93
-                );
94
-            }
95
-            return $controller->sendResponse(
96
-                $controller->getEntitiesFromModel(
97
-                    $controller->getModelVersionInfo()->loadModel($model_name),
98
-                    $request
99
-                )
100
-            );
101
-        } catch (Exception $e) {
102
-            return $controller->sendResponse($e);
103
-        }
104
-    }
105
-
106
-
107
-    /**
108
-     * Prepares and returns schema for any OPTIONS request.
109
-     *
110
-     * @param string $version The API endpoint version being used.
111
-     * @param string $model_name Something like `Event` or `Registration`
112
-     * @return array
113
-     * @throws InvalidArgumentException
114
-     * @throws InvalidDataTypeException
115
-     * @throws InvalidInterfaceException
116
-     */
117
-    public static function handleSchemaRequest($version, $model_name)
118
-    {
119
-        $controller = LoaderFactory::getLoader()->getNew('EventEspresso\core\libraries\rest_api\controllers\model\Read');
120
-        try {
121
-            $controller->setRequestedVersion($version);
122
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
123
-                return array();
124
-            }
125
-            // get the model for this version
126
-            $model = $controller->getModelVersionInfo()->loadModel($model_name);
127
-            $model_schema = new JsonModelSchema($model, LoaderFactory::getLoader()->getShared('EventEspresso\core\libraries\rest_api\CalculatedModelFields'));
128
-            return $model_schema->getModelSchemaForRelations(
129
-                $controller->getModelVersionInfo()->relationSettings($model),
130
-                $controller->customizeSchemaForRestResponse(
131
-                    $model,
132
-                    $model_schema->getModelSchemaForFields(
133
-                        $controller->getModelVersionInfo()->fieldsOnModelInThisVersion($model),
134
-                        $model_schema->getInitialSchemaStructure()
135
-                    )
136
-                )
137
-            );
138
-        } catch (Exception $e) {
139
-            return array();
140
-        }
141
-    }
142
-
143
-
144
-    /**
145
-     * This loops through each field in the given schema for the model and does the following:
146
-     * - add any extra fields that are REST API specific and related to existing fields.
147
-     * - transform default values into the correct format for a REST API response.
148
-     *
149
-     * @param EEM_Base $model
150
-     * @param array    $schema
151
-     * @return array  The final schema.
152
-     */
153
-    protected function customizeSchemaForRestResponse(EEM_Base $model, array $schema)
154
-    {
155
-        foreach ($this->getModelVersionInfo()->fieldsOnModelInThisVersion($model) as $field_name => $field) {
156
-            $schema = $this->translateDefaultsForRestResponse(
157
-                $field_name,
158
-                $field,
159
-                $this->maybeAddExtraFieldsToSchema($field_name, $field, $schema)
160
-            );
161
-        }
162
-        return $schema;
163
-    }
164
-
165
-
166
-    /**
167
-     * This is used to ensure that the 'default' value set in the schema response is formatted correctly for the REST
168
-     * response.
169
-     *
170
-     * @param                      $field_name
171
-     * @param EE_Model_Field_Base  $field
172
-     * @param array                $schema
173
-     * @return array
174
-     * @throws ObjectDetectedException if a default value has a PHP object, which should never do (and if we
175
-     * did, let's know about it ASAP, so let the exception bubble up)
176
-     */
177
-    protected function translateDefaultsForRestResponse($field_name, EE_Model_Field_Base $field, array $schema)
178
-    {
179
-        if (isset($schema['properties'][ $field_name ]['default'])) {
180
-            if (is_array($schema['properties'][ $field_name ]['default'])) {
181
-                foreach ($schema['properties'][ $field_name ]['default'] as $default_key => $default_value) {
182
-                    if ($default_key === 'raw') {
183
-                        $schema['properties'][ $field_name ]['default'][ $default_key ] =
184
-                            ModelDataTranslator::prepareFieldValueForJson(
185
-                                $field,
186
-                                $default_value,
187
-                                $this->getModelVersionInfo()->requestedVersion()
188
-                            );
189
-                    }
190
-                }
191
-            } else {
192
-                $schema['properties'][ $field_name ]['default'] = ModelDataTranslator::prepareFieldValueForJson(
193
-                    $field,
194
-                    $schema['properties'][ $field_name ]['default'],
195
-                    $this->getModelVersionInfo()->requestedVersion()
196
-                );
197
-            }
198
-        }
199
-        return $schema;
200
-    }
201
-
202
-
203
-    /**
204
-     * Adds additional fields to the schema
205
-     * The REST API returns a GMT value field for each datetime field in the resource.  Thus the description about this
206
-     * needs to be added to the schema.
207
-     *
208
-     * @param                      $field_name
209
-     * @param EE_Model_Field_Base  $field
210
-     * @param array                $schema
211
-     * @return array
212
-     */
213
-    protected function maybeAddExtraFieldsToSchema($field_name, EE_Model_Field_Base $field, array $schema)
214
-    {
215
-        if ($field instanceof EE_Datetime_Field) {
216
-            $schema['properties'][ $field_name . '_gmt' ] = $field->getSchema();
217
-            // modify the description
218
-            $schema['properties'][ $field_name . '_gmt' ]['description'] = sprintf(
219
-                esc_html__('%s - the value for this field is in GMT.', 'event_espresso'),
220
-                wp_specialchars_decode($field->get_nicename(), ENT_QUOTES)
221
-            );
222
-        }
223
-        return $schema;
224
-    }
225
-
226
-
227
-    /**
228
-     * Used to figure out the route from the request when a `WP_REST_Request` object is not available
229
-     *
230
-     * @return string
231
-     */
232
-    protected function getRouteFromRequest()
233
-    {
234
-        if (isset($GLOBALS['wp'])
235
-            && $GLOBALS['wp'] instanceof \WP
236
-            && isset($GLOBALS['wp']->query_vars['rest_route'])
237
-        ) {
238
-            return $GLOBALS['wp']->query_vars['rest_route'];
239
-        } else {
240
-            return isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : '/';
241
-        }
242
-    }
243
-
244
-
245
-    /**
246
-     * Gets a single entity related to the model indicated in the path and its id
247
-     *
248
-     * @param WP_REST_Request $request
249
-     * @param string $version
250
-     * @param string $model_name
251
-     * @return WP_REST_Response|WP_Error
252
-     * @throws InvalidDataTypeException
253
-     * @throws InvalidInterfaceException
254
-     * @throws InvalidArgumentException
255
-     */
256
-    public static function handleRequestGetOne(WP_REST_Request $request, $version, $model_name)
257
-    {
258
-        $controller = LoaderFactory::getLoader()->getNew('EventEspresso\core\libraries\rest_api\controllers\model\Read');
259
-        try {
260
-            $controller->setRequestedVersion($version);
261
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
262
-                return $controller->sendResponse(
263
-                    new WP_Error(
264
-                        'endpoint_parsing_error',
265
-                        sprintf(
266
-                            __(
267
-                                'There is no model for endpoint %s. Please contact event espresso support',
268
-                                'event_espresso'
269
-                            ),
270
-                            $model_name
271
-                        )
272
-                    )
273
-                );
274
-            }
275
-            return $controller->sendResponse(
276
-                $controller->getEntityFromModel(
277
-                    $controller->getModelVersionInfo()->loadModel($model_name),
278
-                    $request
279
-                )
280
-            );
281
-        } catch (Exception $e) {
282
-            return $controller->sendResponse($e);
283
-        }
284
-    }
285
-
286
-
287
-    /**
288
-     * Gets all the related entities (or if its a belongs-to relation just the one)
289
-     * to the item with the given id
290
-     *
291
-     * @param WP_REST_Request $request
292
-     * @param string $version
293
-     * @param string $model_name
294
-     * @param string $related_model_name
295
-     * @return WP_REST_Response|WP_Error
296
-     * @throws InvalidDataTypeException
297
-     * @throws InvalidInterfaceException
298
-     * @throws InvalidArgumentException
299
-     */
300
-    public static function handleRequestGetRelated(
301
-        WP_REST_Request $request,
302
-        $version,
303
-        $model_name,
304
-        $related_model_name
305
-    ) {
306
-        $controller = LoaderFactory::getLoader()->getNew('EventEspresso\core\libraries\rest_api\controllers\model\Read');
307
-        try {
308
-            $controller->setRequestedVersion($version);
309
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
310
-                return $controller->sendResponse(
311
-                    new WP_Error(
312
-                        'endpoint_parsing_error',
313
-                        sprintf(
314
-                            __(
315
-                                'There is no model for endpoint %s. Please contact event espresso support',
316
-                                'event_espresso'
317
-                            ),
318
-                            $model_name
319
-                        )
320
-                    )
321
-                );
322
-            }
323
-            $main_model = $controller->getModelVersionInfo()->loadModel($model_name);
324
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($related_model_name)) {
325
-                return $controller->sendResponse(
326
-                    new WP_Error(
327
-                        'endpoint_parsing_error',
328
-                        sprintf(
329
-                            __(
330
-                                'There is no model for endpoint %s. Please contact event espresso support',
331
-                                'event_espresso'
332
-                            ),
333
-                            $related_model_name
334
-                        )
335
-                    )
336
-                );
337
-            }
338
-            return $controller->sendResponse(
339
-                $controller->getEntitiesFromRelation(
340
-                    $request->get_param('id'),
341
-                    $main_model->related_settings_for($related_model_name),
342
-                    $request
343
-                )
344
-            );
345
-        } catch (Exception $e) {
346
-            return $controller->sendResponse($e);
347
-        }
348
-    }
349
-
350
-
351
-    /**
352
-     * Gets a collection for the given model and filters
353
-     *
354
-     * @param EEM_Base $model
355
-     * @param WP_REST_Request $request
356
-     * @return array
357
-     * @throws EE_Error
358
-     * @throws InvalidArgumentException
359
-     * @throws InvalidDataTypeException
360
-     * @throws InvalidInterfaceException
361
-     * @throws ReflectionException
362
-     * @throws RestException
363
-     */
364
-    public function getEntitiesFromModel($model, $request)
365
-    {
366
-        $query_params = $this->createModelQueryParams($model, $request->get_params());
367
-        if (! Capabilities::currentUserHasPartialAccessTo($model, $query_params['caps'])) {
368
-            $model_name_plural = EEH_Inflector::pluralize_and_lower($model->get_this_model_name());
369
-            throw new RestException(
370
-                sprintf('rest_%s_cannot_list', $model_name_plural),
371
-                sprintf(
372
-                    __('Sorry, you are not allowed to list %1$s. Missing permissions: %2$s', 'event_espresso'),
373
-                    $model_name_plural,
374
-                    Capabilities::getMissingPermissionsString($model, $query_params['caps'])
375
-                ),
376
-                array('status' => 403)
377
-            );
378
-        }
379
-        if (! $request->get_header('no_rest_headers')) {
380
-            $this->setHeadersFromQueryParams($model, $query_params);
381
-        }
382
-        /** @type array $results */
383
-        $results = $model->get_all_wpdb_results($query_params);
384
-        $nice_results = array();
385
-        foreach ($results as $result) {
386
-            $nice_results[] =  $this->createEntityFromWpdbResult(
387
-                $model,
388
-                $result,
389
-                $request
390
-            );
391
-        }
392
-        return $nice_results;
393
-    }
394
-
395
-
396
-    /**
397
-     * Gets the collection for given relation object
398
-     * The same as Read::get_entities_from_model(), except if the relation
399
-     * is a HABTM relation, in which case it merges any non-foreign-key fields from
400
-     * the join-model-object into the results
401
-     *
402
-     * @param array $primary_model_query_params query params for finding the item from which
403
-     *                                                            relations will be based
404
-     * @param \EE_Model_Relation_Base $relation
405
-     * @param WP_REST_Request $request
406
-     * @return array
407
-     * @throws EE_Error
408
-     * @throws InvalidArgumentException
409
-     * @throws InvalidDataTypeException
410
-     * @throws InvalidInterfaceException
411
-     * @throws ReflectionException
412
-     * @throws RestException
413
-     * @throws \EventEspresso\core\exceptions\ModelConfigurationException
414
-     */
415
-    protected function getEntitiesFromRelationUsingModelQueryParams($primary_model_query_params, $relation, $request)
416
-    {
417
-        $context = $this->validateContext($request->get_param('caps'));
418
-        $model = $relation->get_this_model();
419
-        $related_model = $relation->get_other_model();
420
-        if (! isset($primary_model_query_params[0])) {
421
-            $primary_model_query_params[0] = array();
422
-        }
423
-        // check if they can access the 1st model object
424
-        $primary_model_query_params = array(
425
-            0       => $primary_model_query_params[0],
426
-            'limit' => 1,
427
-        );
428
-        if ($model instanceof EEM_Soft_Delete_Base) {
429
-            $primary_model_query_params = $model->alter_query_params_so_deleted_and_undeleted_items_included(
430
-                $primary_model_query_params
431
-            );
432
-        }
433
-        $restricted_query_params = $primary_model_query_params;
434
-        $restricted_query_params['caps'] = $context;
435
-        $restricted_query_params['limit'] = 1;
436
-        $this->setDebugInfo('main model query params', $restricted_query_params);
437
-        $this->setDebugInfo('missing caps', Capabilities::getMissingPermissionsString($related_model, $context));
438
-        $primary_model_rows = $model->get_all_wpdb_results($restricted_query_params);
439
-        $primary_model_row = null;
440
-        if (is_array($primary_model_rows)) {
441
-            $primary_model_row = reset($primary_model_rows);
442
-        }
443
-        if (! (
444
-            Capabilities::currentUserHasPartialAccessTo($related_model, $context)
445
-            && $primary_model_row
446
-        )
447
-        ) {
448
-            if ($relation instanceof EE_Belongs_To_Relation) {
449
-                $related_model_name_maybe_plural = strtolower($related_model->get_this_model_name());
450
-            } else {
451
-                $related_model_name_maybe_plural = EEH_Inflector::pluralize_and_lower(
452
-                    $related_model->get_this_model_name()
453
-                );
454
-            }
455
-            throw new RestException(
456
-                sprintf('rest_%s_cannot_list', $related_model_name_maybe_plural),
457
-                sprintf(
458
-                    __(
459
-                        'Sorry, you are not allowed to list %1$s related to %2$s. Missing permissions: %3$s',
460
-                        'event_espresso'
461
-                    ),
462
-                    $related_model_name_maybe_plural,
463
-                    $relation->get_this_model()->get_this_model_name(),
464
-                    implode(
465
-                        ',',
466
-                        array_keys(
467
-                            Capabilities::getMissingPermissions($related_model, $context)
468
-                        )
469
-                    )
470
-                ),
471
-                array('status' => 403)
472
-            );
473
-        }
474
-
475
-        $this->checkPassword(
476
-            $model,
477
-            $primary_model_row,
478
-            $restricted_query_params,
479
-            $request
480
-        );
481
-        $query_params = $this->createModelQueryParams($relation->get_other_model(), $request->get_params());
482
-        foreach ($primary_model_query_params[0] as $where_condition_key => $where_condition_value) {
483
-            $query_params[0][ $relation->get_this_model()->get_this_model_name()
484
-                              . '.'
485
-                              . $where_condition_key ] = $where_condition_value;
486
-        }
487
-        $query_params['default_where_conditions'] = 'none';
488
-        $query_params['caps'] = $context;
489
-        if (! $request->get_header('no_rest_headers')) {
490
-            $this->setHeadersFromQueryParams($relation->get_other_model(), $query_params);
491
-        }
492
-        /** @type array $results */
493
-        $results = $relation->get_other_model()->get_all_wpdb_results($query_params);
494
-        $nice_results = array();
495
-        foreach ($results as $result) {
496
-            $nice_result = $this->createEntityFromWpdbResult(
497
-                $relation->get_other_model(),
498
-                $result,
499
-                $request
500
-            );
501
-            if ($relation instanceof \EE_HABTM_Relation) {
502
-                // put the unusual stuff (properties from the HABTM relation) first, and make sure
503
-                // if there are conflicts we prefer the properties from the main model
504
-                $join_model_result = $this->createEntityFromWpdbResult(
505
-                    $relation->get_join_model(),
506
-                    $result,
507
-                    $request
508
-                );
509
-                $joined_result = array_merge($nice_result, $join_model_result);
510
-                // but keep the meta stuff from the main model
511
-                if (isset($nice_result['meta'])) {
512
-                    $joined_result['meta'] = $nice_result['meta'];
513
-                }
514
-                $nice_result = $joined_result;
515
-            }
516
-            $nice_results[] = $nice_result;
517
-        }
518
-        if ($relation instanceof EE_Belongs_To_Relation) {
519
-            return array_shift($nice_results);
520
-        } else {
521
-            return $nice_results;
522
-        }
523
-    }
524
-
525
-
526
-    /**
527
-     * Gets the collection for given relation object
528
-     * The same as Read::get_entities_from_model(), except if the relation
529
-     * is a HABTM relation, in which case it merges any non-foreign-key fields from
530
-     * the join-model-object into the results
531
-     *
532
-     * @param string                  $id the ID of the thing we are fetching related stuff from
533
-     * @param \EE_Model_Relation_Base $relation
534
-     * @param WP_REST_Request         $request
535
-     * @return array
536
-     * @throws EE_Error
537
-     */
538
-    public function getEntitiesFromRelation($id, $relation, $request)
539
-    {
540
-        if (! $relation->get_this_model()->has_primary_key_field()) {
541
-            throw new EE_Error(
542
-                sprintf(
543
-                    __(
544
-                    // @codingStandardsIgnoreStart
545
-                        'Read::get_entities_from_relation should only be called from a model with a primary key, it was called from %1$s',
546
-                        // @codingStandardsIgnoreEnd
547
-                        'event_espresso'
548
-                    ),
549
-                    $relation->get_this_model()->get_this_model_name()
550
-                )
551
-            );
552
-        }
553
-        // can we edit that main item?
554
-        // if not, show nothing but an error
555
-        // otherwise, please proceed
556
-        return $this->getEntitiesFromRelationUsingModelQueryParams(
557
-            array(
558
-                array(
559
-                    $relation->get_this_model()->primary_key_name() => $id,
560
-                ),
561
-            ),
562
-            $relation,
563
-            $request
564
-        );
565
-    }
566
-
567
-
568
-    /**
569
-     * Sets the headers that are based on the model and query params,
570
-     * like the total records. This should only be called on the original request
571
-     * from the client, not on subsequent internal
572
-     *
573
-     * @param EEM_Base $model
574
-     * @param array    $query_params
575
-     * @return void
576
-     */
577
-    protected function setHeadersFromQueryParams($model, $query_params)
578
-    {
579
-        $this->setDebugInfo('model query params', $query_params);
580
-        $this->setDebugInfo(
581
-            'missing caps',
582
-            Capabilities::getMissingPermissionsString($model, $query_params['caps'])
583
-        );
584
-        // normally the limit to a 2-part array, where the 2nd item is the limit
585
-        if (! isset($query_params['limit'])) {
586
-            $query_params['limit'] = EED_Core_Rest_Api::get_default_query_limit();
587
-        }
588
-        if (is_array($query_params['limit'])) {
589
-            $limit_parts = $query_params['limit'];
590
-        } else {
591
-            $limit_parts = explode(',', $query_params['limit']);
592
-            if (count($limit_parts) == 1) {
593
-                $limit_parts = array(0, $limit_parts[0]);
594
-            }
595
-        }
596
-        // remove the group by and having parts of the query, as those will
597
-        // make the sql query return an array of values, instead of just a single value
598
-        unset($query_params['group_by'], $query_params['having'], $query_params['limit']);
599
-        $count = $model->count($query_params, null, true);
600
-        $pages = $count / $limit_parts[1];
601
-        $this->setResponseHeader('Total', $count, false);
602
-        $this->setResponseHeader('PageSize', $limit_parts[1], false);
603
-        $this->setResponseHeader('TotalPages', ceil($pages), false);
604
-    }
605
-
606
-
607
-    /**
608
-     * Changes database results into REST API entities
609
-     *
610
-     * @param EEM_Base $model
611
-     * @param array $db_row like results from $wpdb->get_results()
612
-     * @param WP_REST_Request $rest_request
613
-     * @param string $deprecated no longer used
614
-     * @return array ready for being converted into json for sending to client
615
-     * @throws EE_Error
616
-     * @throws RestException
617
-     * @throws InvalidDataTypeException
618
-     * @throws InvalidInterfaceException
619
-     * @throws InvalidArgumentException
620
-     * @throws ReflectionException
621
-     */
622
-    public function createEntityFromWpdbResult($model, $db_row, $rest_request, $deprecated = null)
623
-    {
624
-        if (! $rest_request instanceof WP_REST_Request) {
625
-            // ok so this was called in the old style, where the 3rd arg was
626
-            // $include, and the 4th arg was $context
627
-            // now setup the request just to avoid fatal errors, although we won't be able
628
-            // to truly make use of it because it's kinda devoid of info
629
-            $rest_request = new WP_REST_Request();
630
-            $rest_request->set_param('include', $rest_request);
631
-            $rest_request->set_param('caps', $deprecated);
632
-        }
633
-        if ($rest_request->get_param('caps') == null) {
634
-            $rest_request->set_param('caps', EEM_Base::caps_read);
635
-        }
636
-        $current_user_full_access_to_entity = $model->currentUserCan(
637
-            EEM_Base::caps_read_admin,
638
-            $model->deduce_fields_n_values_from_cols_n_values($db_row)
639
-        );
640
-        $entity_array = $this->createBareEntityFromWpdbResults($model, $db_row);
641
-        $entity_array = $this->addExtraFields($model, $db_row, $entity_array);
642
-        $entity_array['_links'] = $this->getEntityLinks($model, $db_row, $entity_array);
643
-        // when it's a regular read request for a model with a password and the password wasn't provided
644
-        // remove the password protected fields
645
-        $has_protected_fields = false;
646
-        try {
647
-            $this->checkPassword(
648
-                $model,
649
-                $db_row,
650
-                $model->alter_query_params_to_restrict_by_ID(
651
-                    $model->get_index_primary_key_string(
652
-                        $model->deduce_fields_n_values_from_cols_n_values($db_row)
653
-                    )
654
-                ),
655
-                $rest_request
656
-            );
657
-        } catch (RestPasswordRequiredException $e) {
658
-            if ($model->hasPassword()) {
659
-                // just remove protected fields
660
-                $has_protected_fields = true;
661
-                $entity_array = Capabilities::filterOutPasswordProtectedFields(
662
-                    $entity_array,
663
-                    $model,
664
-                    $this->getModelVersionInfo()
665
-                );
666
-            } else {
667
-                // that's a problem. None of this should be accessible if no password was provided
668
-                throw $e;
669
-            }
670
-        }
671
-
672
-        $entity_array['_calculated_fields'] = $this->getEntityCalculations($model, $db_row, $rest_request, $has_protected_fields);
673
-        $entity_array = apply_filters(
674
-            'FHEE__Read__create_entity_from_wpdb_results__entity_before_including_requested_models',
675
-            $entity_array,
676
-            $model,
677
-            $rest_request->get_param('caps'),
678
-            $rest_request,
679
-            $this
680
-        );
681
-        // add an empty protected property for now. If it's still around after we remove everything the request didn't
682
-        // want, we'll populate it then. k?
683
-        $entity_array['_protected'] = array();
684
-        // remove any properties the request didn't want. This way _protected won't bother mentioning them
685
-        $entity_array = $this->includeOnlyRequestedProperties($model, $rest_request, $entity_array);
686
-        $entity_array = $this->includeRequestedModels($model, $rest_request, $entity_array, $db_row, $has_protected_fields);
687
-        // if they still wanted the _protected property, add it.
688
-        if (isset($entity_array['_protected'])) {
689
-            $entity_array = $this->addProtectedProperty($model, $entity_array, $has_protected_fields);
690
-        }
691
-        $entity_array = apply_filters(
692
-            'FHEE__Read__create_entity_from_wpdb_results__entity_before_inaccessible_field_removal',
693
-            $entity_array,
694
-            $model,
695
-            $rest_request->get_param('caps'),
696
-            $rest_request,
697
-            $this
698
-        );
699
-        if (! $current_user_full_access_to_entity) {
700
-            $result_without_inaccessible_fields = Capabilities::filterOutInaccessibleEntityFields(
701
-                $entity_array,
702
-                $model,
703
-                $rest_request->get_param('caps'),
704
-                $this->getModelVersionInfo()
705
-            );
706
-        } else {
707
-            $result_without_inaccessible_fields = $entity_array;
708
-        }
709
-        $this->setDebugInfo(
710
-            'inaccessible fields',
711
-            array_keys(array_diff_key((array) $entity_array, (array) $result_without_inaccessible_fields))
712
-        );
713
-        return apply_filters(
714
-            'FHEE__Read__create_entity_from_wpdb_results__entity_return',
715
-            $result_without_inaccessible_fields,
716
-            $model,
717
-            $rest_request->get_param('caps')
718
-        );
719
-    }
720
-
721
-    /**
722
-     * Returns an array describing which fields can be protected, and which actually were removed this request
723
-     * @since 4.9.74.p
724
-     * @param $model
725
-     * @param $results_so_far
726
-     * @param $protected
727
-     * @return array results
728
-     */
729
-    protected function addProtectedProperty(EEM_Base $model, $results_so_far, $protected)
730
-    {
731
-        if (! $model->hasPassword() || ! $protected) {
732
-            return $results_so_far;
733
-        }
734
-        $password_field = $model->getPasswordField();
735
-        $all_protected = array_merge(
736
-            array($password_field->get_name()),
737
-            $password_field->protectedFields()
738
-        );
739
-        $fields_included = array_keys($results_so_far);
740
-        $fields_included = array_intersect(
741
-            $all_protected,
742
-            $fields_included
743
-        );
744
-        foreach ($fields_included as $field_name) {
745
-            $results_so_far['_protected'][] = $field_name ;
746
-        }
747
-        return $results_so_far;
748
-    }
749
-
750
-    /**
751
-     * Creates a REST entity array (JSON object we're going to return in the response, but
752
-     * for now still a PHP array, but soon enough we'll call json_encode on it, don't worry),
753
-     * from $wpdb->get_row( $sql, ARRAY_A)
754
-     *
755
-     * @param EEM_Base $model
756
-     * @param array    $db_row
757
-     * @return array entity mostly ready for converting to JSON and sending in the response
758
-     */
759
-    protected function createBareEntityFromWpdbResults(EEM_Base $model, $db_row)
760
-    {
761
-        $result = $model->deduce_fields_n_values_from_cols_n_values($db_row);
762
-        $result = array_intersect_key(
763
-            $result,
764
-            $this->getModelVersionInfo()->fieldsOnModelInThisVersion($model)
765
-        );
766
-        // if this is a CPT, we need to set the global $post to it,
767
-        // otherwise shortcodes etc won't work properly while rendering it
768
-        if ($model instanceof \EEM_CPT_Base) {
769
-            $do_chevy_shuffle = true;
770
-        } else {
771
-            $do_chevy_shuffle = false;
772
-        }
773
-        if ($do_chevy_shuffle) {
774
-            global $post;
775
-            $old_post = $post;
776
-            $post = get_post($result[ $model->primary_key_name() ]);
777
-            if (! $post instanceof \WP_Post) {
778
-                // well that's weird, because $result is what we JUST fetched from the database
779
-                throw new RestException(
780
-                    'error_fetching_post_from_database_results',
781
-                    esc_html__(
782
-                        'An item was retrieved from the database but it\'s not a WP_Post like it should be.',
783
-                        'event_espresso'
784
-                    )
785
-                );
786
-            }
787
-            $model_object_classname = 'EE_' . $model->get_this_model_name();
788
-            $post->{$model_object_classname} = \EE_Registry::instance()->load_class(
789
-                $model_object_classname,
790
-                $result,
791
-                false,
792
-                false
793
-            );
794
-        }
795
-        foreach ($result as $field_name => $field_value) {
796
-            $field_obj = $model->field_settings_for($field_name);
797
-            if ($this->isSubclassOfOne($field_obj, $this->getModelVersionInfo()->fieldsIgnored())) {
798
-                unset($result[ $field_name ]);
799
-            } elseif ($this->isSubclassOfOne(
800
-                $field_obj,
801
-                $this->getModelVersionInfo()->fieldsThatHaveRenderedFormat()
802
-            )
803
-            ) {
804
-                $result[ $field_name ] = array(
805
-                    'raw'      => $this->prepareFieldObjValueForJson($field_obj, $field_value),
806
-                    'rendered' => $this->prepareFieldObjValueForJson($field_obj, $field_value, 'pretty'),
807
-                );
808
-            } elseif ($this->isSubclassOfOne(
809
-                $field_obj,
810
-                $this->getModelVersionInfo()->fieldsThatHavePrettyFormat()
811
-            )
812
-            ) {
813
-                $result[ $field_name ] = array(
814
-                    'raw'    => $this->prepareFieldObjValueForJson($field_obj, $field_value),
815
-                    'pretty' => $this->prepareFieldObjValueForJson($field_obj, $field_value, 'pretty'),
816
-                );
817
-            } elseif ($field_obj instanceof \EE_Datetime_Field) {
818
-                $field_value = $field_obj->prepare_for_set_from_db($field_value);
819
-                // if the value is null, but we're not supposed to permit null, then set to the field's default
820
-                if (is_null($field_value)) {
821
-                    $field_value = $field_obj->getDefaultDateTimeObj();
822
-                }
823
-                if (is_null($field_value)) {
824
-                    $gmt_date = $local_date = ModelDataTranslator::prepareFieldValuesForJson(
825
-                        $field_obj,
826
-                        $field_value,
827
-                        $this->getModelVersionInfo()->requestedVersion()
828
-                    );
829
-                } else {
830
-                    $timezone = $field_value->getTimezone();
831
-                    EEH_DTT_Helper::setTimezone($field_value, new DateTimeZone('UTC'));
832
-                    $gmt_date = ModelDataTranslator::prepareFieldValuesForJson(
833
-                        $field_obj,
834
-                        $field_value,
835
-                        $this->getModelVersionInfo()->requestedVersion()
836
-                    );
837
-                    EEH_DTT_Helper::setTimezone($field_value, $timezone);
838
-                    $local_date = ModelDataTranslator::prepareFieldValuesForJson(
839
-                        $field_obj,
840
-                        $field_value,
841
-                        $this->getModelVersionInfo()->requestedVersion()
842
-                    );
843
-                }
844
-                $result[ $field_name . '_gmt' ] = $gmt_date;
845
-                $result[ $field_name ] = $local_date;
846
-            } else {
847
-                $result[ $field_name ] = $this->prepareFieldObjValueForJson($field_obj, $field_value);
848
-            }
849
-        }
850
-        if ($do_chevy_shuffle) {
851
-            $post = $old_post;
852
-        }
853
-        return $result;
854
-    }
855
-
856
-
857
-    /**
858
-     * Takes a value all the way from the DB representation, to the model object's representation, to the
859
-     * user-facing PHP representation, to the REST API representation. (Assumes you've already taken from the DB
860
-     * representation using $field_obj->prepare_for_set_from_db())
861
-     *
862
-     * @param EE_Model_Field_Base $field_obj
863
-     * @param mixed               $value  as it's stored on a model object
864
-     * @param string              $format valid values are 'normal' (default), 'pretty', 'datetime_obj'
865
-     * @return mixed
866
-     * @throws ObjectDetectedException if $value contains a PHP object
867
-     */
868
-    protected function prepareFieldObjValueForJson(EE_Model_Field_Base $field_obj, $value, $format = 'normal')
869
-    {
870
-        $value = $field_obj->prepare_for_set_from_db($value);
871
-        switch ($format) {
872
-            case 'pretty':
873
-                $value = $field_obj->prepare_for_pretty_echoing($value);
874
-                break;
875
-            case 'normal':
876
-            default:
877
-                $value = $field_obj->prepare_for_get($value);
878
-                break;
879
-        }
880
-        return ModelDataTranslator::prepareFieldValuesForJson(
881
-            $field_obj,
882
-            $value,
883
-            $this->getModelVersionInfo()->requestedVersion()
884
-        );
885
-    }
886
-
887
-
888
-    /**
889
-     * Adds a few extra fields to the entity response
890
-     *
891
-     * @param EEM_Base $model
892
-     * @param array    $db_row
893
-     * @param array    $entity_array
894
-     * @return array modified entity
895
-     */
896
-    protected function addExtraFields(EEM_Base $model, $db_row, $entity_array)
897
-    {
898
-        if ($model instanceof EEM_CPT_Base) {
899
-            $entity_array['link'] = get_permalink($db_row[ $model->get_primary_key_field()->get_qualified_column() ]);
900
-        }
901
-        return $entity_array;
902
-    }
903
-
904
-
905
-    /**
906
-     * Gets links we want to add to the response
907
-     *
908
-     * @global \WP_REST_Server $wp_rest_server
909
-     * @param EEM_Base         $model
910
-     * @param array            $db_row
911
-     * @param array            $entity_array
912
-     * @return array the _links item in the entity
913
-     */
914
-    protected function getEntityLinks($model, $db_row, $entity_array)
915
-    {
916
-        // add basic links
917
-        $links = array();
918
-        if ($model->has_primary_key_field()) {
919
-            $links['self'] = array(
920
-                array(
921
-                    'href' => $this->getVersionedLinkTo(
922
-                        EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
923
-                        . '/'
924
-                        . $entity_array[ $model->primary_key_name() ]
925
-                    ),
926
-                ),
927
-            );
928
-        }
929
-        $links['collection'] = array(
930
-            array(
931
-                'href' => $this->getVersionedLinkTo(
932
-                    EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
933
-                ),
934
-            ),
935
-        );
936
-        // add links to related models
937
-        if ($model->has_primary_key_field()) {
938
-            foreach ($this->getModelVersionInfo()->relationSettings($model) as $relation_name => $relation_obj) {
939
-                $related_model_part = Read::getRelatedEntityName($relation_name, $relation_obj);
940
-                $links[ EED_Core_Rest_Api::ee_api_link_namespace . $related_model_part ] = array(
941
-                    array(
942
-                        'href'   => $this->getVersionedLinkTo(
943
-                            EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
944
-                            . '/'
945
-                            . $entity_array[ $model->primary_key_name() ]
946
-                            . '/'
947
-                            . $related_model_part
948
-                        ),
949
-                        'single' => $relation_obj instanceof EE_Belongs_To_Relation ? true : false,
950
-                    ),
951
-                );
952
-            }
953
-        }
954
-        return $links;
955
-    }
956
-
957
-
958
-    /**
959
-     * Adds the included models indicated in the request to the entity provided
960
-     *
961
-     * @param EEM_Base $model
962
-     * @param WP_REST_Request $rest_request
963
-     * @param array $entity_array
964
-     * @param array $db_row
965
-     * @param boolean $included_items_protected if the original item is password protected, don't include any related models.
966
-     * @return array the modified entity
967
-     * @throws RestException
968
-     */
969
-    protected function includeRequestedModels(
970
-        EEM_Base $model,
971
-        WP_REST_Request $rest_request,
972
-        $entity_array,
973
-        $db_row = array(),
974
-        $included_items_protected = false
975
-    ) {
976
-        // if $db_row not included, hope the entity array has what we need
977
-        if (! $db_row) {
978
-            $db_row = $entity_array;
979
-        }
980
-        $relation_settings = $this->getModelVersionInfo()->relationSettings($model);
981
-        foreach ($relation_settings as $relation_name => $relation_obj) {
982
-            $related_fields_to_include = $this->explodeAndGetItemsPrefixedWith(
983
-                $rest_request->get_param('include'),
984
-                $relation_name
985
-            );
986
-            $related_fields_to_calculate = $this->explodeAndGetItemsPrefixedWith(
987
-                $rest_request->get_param('calculate'),
988
-                $relation_name
989
-            );
990
-            // did they specify they wanted to include a related model, or
991
-            // specific fields from a related model?
992
-            // or did they specify to calculate a field from a related model?
993
-            if ($related_fields_to_include || $related_fields_to_calculate) {
994
-                // if so, we should include at least some part of the related model
995
-                $pretend_related_request = new WP_REST_Request();
996
-                $pretend_related_request->set_query_params(
997
-                    array(
998
-                        'caps'      => $rest_request->get_param('caps'),
999
-                        'include'   => $related_fields_to_include,
1000
-                        'calculate' => $related_fields_to_calculate,
1001
-                        'password' => $rest_request->get_param('password')
1002
-                    )
1003
-                );
1004
-                $pretend_related_request->add_header('no_rest_headers', true);
1005
-                $primary_model_query_params = $model->alter_query_params_to_restrict_by_ID(
1006
-                    $model->get_index_primary_key_string(
1007
-                        $model->deduce_fields_n_values_from_cols_n_values($db_row)
1008
-                    )
1009
-                );
1010
-                if (! $included_items_protected) {
1011
-                    $related_results = $this->getEntitiesFromRelationUsingModelQueryParams(
1012
-                        $primary_model_query_params,
1013
-                        $relation_obj,
1014
-                        $pretend_related_request
1015
-                    );
1016
-                } else {
1017
-                    // they're protected, hide them.
1018
-                    $related_results = $relation_obj instanceof EE_Belongs_To_Relation ? null : array();
1019
-                    $entity_array['_protected'][] = Read::getRelatedEntityName($relation_name, $relation_obj);
1020
-                }
1021
-                if ($related_results instanceof WP_Error) {
1022
-                    $related_results = null;
1023
-                }
1024
-                $entity_array[ Read::getRelatedEntityName($relation_name, $relation_obj) ] = $related_results;
1025
-            }
1026
-        }
1027
-        return $entity_array;
1028
-    }
1029
-
1030
-    /**
1031
-     * If the user has requested only specific properties (including meta properties like _links or _protected)
1032
-     * remove everything else.
1033
-     * @since 4.9.74.p
1034
-     * @param EEM_Base $model
1035
-     * @param WP_REST_Request $rest_request
1036
-     * @param $entity_array
1037
-     * @return array
1038
-     * @throws EE_Error
1039
-     */
1040
-    protected function includeOnlyRequestedProperties(
1041
-        EEM_Base $model,
1042
-        WP_REST_Request $rest_request,
1043
-        $entity_array
1044
-    ) {
1045
-
1046
-        $includes_for_this_model = $this->explodeAndGetItemsPrefixedWith($rest_request->get_param('include'), '');
1047
-        $includes_for_this_model = $this->removeModelNamesFromArray($includes_for_this_model);
1048
-        // if they passed in * or didn't specify any includes, return everything
1049
-        if (! in_array('*', $includes_for_this_model)
1050
-            && ! empty($includes_for_this_model)
1051
-        ) {
1052
-            if ($model->has_primary_key_field()) {
1053
-                // always include the primary key. ya just gotta know that at least
1054
-                $includes_for_this_model[] = $model->primary_key_name();
1055
-            }
1056
-            if ($this->explodeAndGetItemsPrefixedWith($rest_request->get_param('calculate'), '')) {
1057
-                $includes_for_this_model[] = '_calculated_fields';
1058
-            }
1059
-            $entity_array = array_intersect_key($entity_array, array_flip($includes_for_this_model));
1060
-        }
1061
-        return $entity_array;
1062
-    }
1063
-
1064
-
1065
-    /**
1066
-     * Returns a new array with all the names of models removed. Eg
1067
-     * array( 'Event', 'Datetime.*', 'foobar' ) would become array( 'Datetime.*', 'foobar' )
1068
-     *
1069
-     * @param array $arr
1070
-     * @return array
1071
-     */
1072
-    private function removeModelNamesFromArray($arr)
1073
-    {
1074
-        return array_diff($arr, array_keys(EE_Registry::instance()->non_abstract_db_models));
1075
-    }
1076
-
1077
-
1078
-    /**
1079
-     * Gets the calculated fields for the response
1080
-     *
1081
-     * @param EEM_Base        $model
1082
-     * @param array           $wpdb_row
1083
-     * @param WP_REST_Request $rest_request
1084
-     * @param boolean $row_is_protected whether this row is password protected or not
1085
-     * @return \stdClass the _calculations item in the entity
1086
-     * @throws ObjectDetectedException if a default value has a PHP object, which should never do (and if we
1087
-     * did, let's know about it ASAP, so let the exception bubble up)
1088
-     */
1089
-    protected function getEntityCalculations($model, $wpdb_row, $rest_request, $row_is_protected = false)
1090
-    {
1091
-        $calculated_fields = $this->explodeAndGetItemsPrefixedWith(
1092
-            $rest_request->get_param('calculate'),
1093
-            ''
1094
-        );
1095
-        // note: setting calculate=* doesn't do anything
1096
-        $calculated_fields_to_return = new \stdClass();
1097
-        $protected_fields = array();
1098
-        foreach ($calculated_fields as $field_to_calculate) {
1099
-            try {
1100
-                // it's password protected, so they shouldn't be able to read this. Remove the value
1101
-                $schema = $this->fields_calculator->getJsonSchemaForModel($model);
1102
-                if ($row_is_protected
1103
-                    && isset($schema['properties'][ $field_to_calculate ]['protected'])
1104
-                    && $schema['properties'][ $field_to_calculate ]['protected']) {
1105
-                    $calculated_value = null;
1106
-                    $protected_fields[] = $field_to_calculate;
1107
-                    if ($schema['properties'][ $field_to_calculate ]['type']) {
1108
-                        switch ($schema['properties'][ $field_to_calculate ]['type']) {
1109
-                            case 'boolean':
1110
-                                $calculated_value = false;
1111
-                                break;
1112
-                            case 'integer':
1113
-                                $calculated_value = 0;
1114
-                                break;
1115
-                            case 'string':
1116
-                                $calculated_value = '';
1117
-                                break;
1118
-                            case 'array':
1119
-                                $calculated_value = array();
1120
-                                break;
1121
-                            case 'object':
1122
-                                $calculated_value = new stdClass();
1123
-                                break;
1124
-                        }
1125
-                    }
1126
-                } else {
1127
-                    $calculated_value = ModelDataTranslator::prepareFieldValueForJson(
1128
-                        null,
1129
-                        $this->fields_calculator->retrieveCalculatedFieldValue(
1130
-                            $model,
1131
-                            $field_to_calculate,
1132
-                            $wpdb_row,
1133
-                            $rest_request,
1134
-                            $this
1135
-                        ),
1136
-                        $this->getModelVersionInfo()->requestedVersion()
1137
-                    );
1138
-                }
1139
-                $calculated_fields_to_return->{$field_to_calculate} = $calculated_value;
1140
-            } catch (RestException $e) {
1141
-                // if we don't have permission to read it, just leave it out. but let devs know about the problem
1142
-                $this->setResponseHeader(
1143
-                    'Notices-Field-Calculation-Errors['
1144
-                    . $e->getStringCode()
1145
-                    . ']['
1146
-                    . $model->get_this_model_name()
1147
-                    . ']['
1148
-                    . $field_to_calculate
1149
-                    . ']',
1150
-                    $e->getMessage(),
1151
-                    true
1152
-                );
1153
-            }
1154
-        }
1155
-        $calculated_fields_to_return->_protected = $protected_fields;
1156
-        return $calculated_fields_to_return;
1157
-    }
1158
-
1159
-
1160
-    /**
1161
-     * Gets the full URL to the resource, taking the requested version into account
1162
-     *
1163
-     * @param string $link_part_after_version_and_slash eg "events/10/datetimes"
1164
-     * @return string url eg "http://mysite.com/wp-json/ee/v4.6/events/10/datetimes"
1165
-     */
1166
-    public function getVersionedLinkTo($link_part_after_version_and_slash)
1167
-    {
1168
-        return rest_url(
1169
-            EED_Core_Rest_Api::get_versioned_route_to(
1170
-                $link_part_after_version_and_slash,
1171
-                $this->getModelVersionInfo()->requestedVersion()
1172
-            )
1173
-        );
1174
-    }
1175
-
1176
-
1177
-    /**
1178
-     * Gets the correct lowercase name for the relation in the API according
1179
-     * to the relation's type
1180
-     *
1181
-     * @param string                  $relation_name
1182
-     * @param \EE_Model_Relation_Base $relation_obj
1183
-     * @return string
1184
-     */
1185
-    public static function getRelatedEntityName($relation_name, $relation_obj)
1186
-    {
1187
-        if ($relation_obj instanceof EE_Belongs_To_Relation) {
1188
-            return strtolower($relation_name);
1189
-        } else {
1190
-            return EEH_Inflector::pluralize_and_lower($relation_name);
1191
-        }
1192
-    }
1193
-
1194
-
1195
-    /**
1196
-     * Gets the one model object with the specified id for the specified model
1197
-     *
1198
-     * @param EEM_Base        $model
1199
-     * @param WP_REST_Request $request
1200
-     * @return array
1201
-     */
1202
-    public function getEntityFromModel($model, $request)
1203
-    {
1204
-        $context = $this->validateContext($request->get_param('caps'));
1205
-        return $this->getOneOrReportPermissionError($model, $request, $context);
1206
-    }
1207
-
1208
-
1209
-    /**
1210
-     * If a context is provided which isn't valid, maybe it was added in a future
1211
-     * version so just treat it as a default read
1212
-     *
1213
-     * @param string $context
1214
-     * @return string array key of EEM_Base::cap_contexts_to_cap_action_map()
1215
-     */
1216
-    public function validateContext($context)
1217
-    {
1218
-        if (! $context) {
1219
-            $context = EEM_Base::caps_read;
1220
-        }
1221
-        $valid_contexts = EEM_Base::valid_cap_contexts();
1222
-        if (in_array($context, $valid_contexts)) {
1223
-            return $context;
1224
-        } else {
1225
-            return EEM_Base::caps_read;
1226
-        }
1227
-    }
1228
-
1229
-
1230
-    /**
1231
-     * Verifies the passed in value is an allowable default where conditions value.
1232
-     *
1233
-     * @param $default_query_params
1234
-     * @return string
1235
-     */
1236
-    public function validateDefaultQueryParams($default_query_params)
1237
-    {
1238
-        $valid_default_where_conditions_for_api_calls = array(
1239
-            EEM_Base::default_where_conditions_all,
1240
-            EEM_Base::default_where_conditions_minimum_all,
1241
-            EEM_Base::default_where_conditions_minimum_others,
1242
-        );
1243
-        if (! $default_query_params) {
1244
-            $default_query_params = EEM_Base::default_where_conditions_all;
1245
-        }
1246
-        if (in_array(
1247
-            $default_query_params,
1248
-            $valid_default_where_conditions_for_api_calls,
1249
-            true
1250
-        )) {
1251
-            return $default_query_params;
1252
-        } else {
1253
-            return EEM_Base::default_where_conditions_all;
1254
-        }
1255
-    }
1256
-
1257
-
1258
-    /**
1259
-     * Translates API filter get parameter into model query params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions.
1260
-     * Note: right now the query parameter keys for fields (and related fields)
1261
-     * can be left as-is, but it's quite possible this will change someday.
1262
-     * Also, this method's contents might be candidate for moving to Model_Data_Translator
1263
-     *
1264
-     * @param EEM_Base $model
1265
-     * @param array    $query_parameters  from $_GET parameter @see Read:handle_request_get_all
1266
-     * @return array model query params (@see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions)
1267
-     *                                    or FALSE to indicate that absolutely no results should be returned
1268
-     * @throws EE_Error
1269
-     * @throws RestException
1270
-     */
1271
-    public function createModelQueryParams($model, $query_params)
1272
-    {
1273
-        $model_query_params = array();
1274
-        if (isset($query_params['where'])) {
1275
-            $model_query_params[0] = ModelDataTranslator::prepareConditionsQueryParamsForModels(
1276
-                $query_params['where'],
1277
-                $model,
1278
-                $this->getModelVersionInfo()->requestedVersion()
1279
-            );
1280
-        }
1281
-        if (isset($query_params['order_by'])) {
1282
-            $order_by = $query_params['order_by'];
1283
-        } elseif (isset($query_params['orderby'])) {
1284
-            $order_by = $query_params['orderby'];
1285
-        } else {
1286
-            $order_by = null;
1287
-        }
1288
-        if ($order_by !== null) {
1289
-            if (is_array($order_by)) {
1290
-                $order_by = ModelDataTranslator::prepareFieldNamesInArrayKeysFromJson($order_by);
1291
-            } else {
1292
-                // it's a single item
1293
-                $order_by = ModelDataTranslator::prepareFieldNameFromJson($order_by);
1294
-            }
1295
-            $model_query_params['order_by'] = $order_by;
1296
-        }
1297
-        if (isset($query_params['group_by'])) {
1298
-            $group_by = $query_params['group_by'];
1299
-        } elseif (isset($query_params['groupby'])) {
1300
-            $group_by = $query_params['groupby'];
1301
-        } else {
1302
-            $group_by = array_keys($model->get_combined_primary_key_fields());
1303
-        }
1304
-        // make sure they're all real names
1305
-        if (is_array($group_by)) {
1306
-            $group_by = ModelDataTranslator::prepareFieldNamesFromJson($group_by);
1307
-        }
1308
-        if ($group_by !== null) {
1309
-            $model_query_params['group_by'] = $group_by;
1310
-        }
1311
-        if (isset($query_params['having'])) {
1312
-            $model_query_params['having'] = ModelDataTranslator::prepareConditionsQueryParamsForModels(
1313
-                $query_params['having'],
1314
-                $model,
1315
-                $this->getModelVersionInfo()->requestedVersion()
1316
-            );
1317
-        }
1318
-        if (isset($query_params['order'])) {
1319
-            $model_query_params['order'] = $query_params['order'];
1320
-        }
1321
-        if (isset($query_params['mine'])) {
1322
-            $model_query_params = $model->alter_query_params_to_only_include_mine($model_query_params);
1323
-        }
1324
-        if (isset($query_params['limit'])) {
1325
-            // limit should be either a string like '23' or '23,43', or an array with two items in it
1326
-            if (! is_array($query_params['limit'])) {
1327
-                $limit_array = explode(',', (string) $query_params['limit']);
1328
-            } else {
1329
-                $limit_array = $query_params['limit'];
1330
-            }
1331
-            $sanitized_limit = array();
1332
-            foreach ($limit_array as $key => $limit_part) {
1333
-                if ($this->debug_mode && (! is_numeric($limit_part) || count($sanitized_limit) > 2)) {
1334
-                    throw new EE_Error(
1335
-                        sprintf(
1336
-                            __(
1337
-                            // @codingStandardsIgnoreStart
1338
-                                'An invalid limit filter was provided. It was: %s. If the EE4 JSON REST API weren\'t in debug mode, this message would not appear.',
1339
-                                // @codingStandardsIgnoreEnd
1340
-                                'event_espresso'
1341
-                            ),
1342
-                            wp_json_encode($query_params['limit'])
1343
-                        )
1344
-                    );
1345
-                }
1346
-                $sanitized_limit[] = (int) $limit_part;
1347
-            }
1348
-            $model_query_params['limit'] = implode(',', $sanitized_limit);
1349
-        } else {
1350
-            $model_query_params['limit'] = EED_Core_Rest_Api::get_default_query_limit();
1351
-        }
1352
-        if (isset($query_params['caps'])) {
1353
-            $model_query_params['caps'] = $this->validateContext($query_params['caps']);
1354
-        } else {
1355
-            $model_query_params['caps'] = EEM_Base::caps_read;
1356
-        }
1357
-        if (isset($query_params['default_where_conditions'])) {
1358
-            $model_query_params['default_where_conditions'] = $this->validateDefaultQueryParams(
1359
-                $query_params['default_where_conditions']
1360
-            );
1361
-        }
1362
-        // if this is a model protected by a password on another model, exclude the password protected
1363
-        // entities by default. But if they passed in a password, try to show them all. If the password is wrong,
1364
-        // though, they'll get an error (see Read::createEntityFromWpdbResult() which calls Read::checkPassword)
1365
-        if (! $model->hasPassword()
1366
-            && $model->restrictedByRelatedModelPassword()
1367
-            && $model_query_params['caps'] === EEM_Base::caps_read) {
1368
-            if (empty($query_params['password'])) {
1369
-                $model_query_params['exclude_protected'] = true;
1370
-            }
1371
-        }
1372
-
1373
-        return apply_filters('FHEE__Read__create_model_query_params', $model_query_params, $query_params, $model);
1374
-    }
1375
-
1376
-
1377
-    /**
1378
-     * Changes the REST-style query params for use in the models
1379
-     *
1380
-     * @deprecated
1381
-     * @param EEM_Base $model
1382
-     * @param array    $query_params sub-array from @see EEM_Base::get_all()
1383
-     * @return array
1384
-     */
1385
-    public function prepareRestQueryParamsKeyForModels($model, $query_params)
1386
-    {
1387
-        $model_ready_query_params = array();
1388
-        foreach ($query_params as $key => $value) {
1389
-            if (is_array($value)) {
1390
-                $model_ready_query_params[ $key ] = $this->prepareRestQueryParamsKeyForModels($model, $value);
1391
-            } else {
1392
-                $model_ready_query_params[ $key ] = $value;
1393
-            }
1394
-        }
1395
-        return $model_ready_query_params;
1396
-    }
1397
-
1398
-
1399
-    /**
1400
-     * @deprecated instead use ModelDataTranslator::prepareFieldValuesFromJson()
1401
-     * @param $model
1402
-     * @param $query_params
1403
-     * @return array
1404
-     */
1405
-    public function prepareRestQueryParamsValuesForModels($model, $query_params)
1406
-    {
1407
-        $model_ready_query_params = array();
1408
-        foreach ($query_params as $key => $value) {
1409
-            if (is_array($value)) {
1410
-                $model_ready_query_params[ $key ] = $this->prepareRestQueryParamsValuesForModels($model, $value);
1411
-            } else {
1412
-                $model_ready_query_params[ $key ] = $value;
1413
-            }
1414
-        }
1415
-        return $model_ready_query_params;
1416
-    }
1417
-
1418
-
1419
-    /**
1420
-     * Explodes the string on commas, and only returns items with $prefix followed by a period.
1421
-     * If no prefix is specified, returns items with no period.
1422
-     *
1423
-     * @param string|array $string_to_explode eg "jibba,jabba, blah, blah, blah" or array('jibba', 'jabba' )
1424
-     * @param string       $prefix            "Event" or "foobar"
1425
-     * @return array $string_to_exploded exploded on COMMAS, and if a prefix was specified
1426
-     *                                        we only return strings starting with that and a period; if no prefix was
1427
-     *                                        specified we return all items containing NO periods
1428
-     */
1429
-    public function explodeAndGetItemsPrefixedWith($string_to_explode, $prefix)
1430
-    {
1431
-        if (is_string($string_to_explode)) {
1432
-            $exploded_contents = explode(',', $string_to_explode);
1433
-        } elseif (is_array($string_to_explode)) {
1434
-            $exploded_contents = $string_to_explode;
1435
-        } else {
1436
-            $exploded_contents = array();
1437
-        }
1438
-        // if the string was empty, we want an empty array
1439
-        $exploded_contents = array_filter($exploded_contents);
1440
-        $contents_with_prefix = array();
1441
-        foreach ($exploded_contents as $item) {
1442
-            $item = trim($item);
1443
-            // if no prefix was provided, so we look for items with no "." in them
1444
-            if (! $prefix) {
1445
-                // does this item have a period?
1446
-                if (strpos($item, '.') === false) {
1447
-                    // if not, then its what we're looking for
1448
-                    $contents_with_prefix[] = $item;
1449
-                }
1450
-            } elseif (strpos($item, $prefix . '.') === 0) {
1451
-                // this item has the prefix and a period, grab it
1452
-                $contents_with_prefix[] = substr(
1453
-                    $item,
1454
-                    strpos($item, $prefix . '.') + strlen($prefix . '.')
1455
-                );
1456
-            } elseif ($item === $prefix) {
1457
-                // this item is JUST the prefix
1458
-                // so let's grab everything after, which is a blank string
1459
-                $contents_with_prefix[] = '';
1460
-            }
1461
-        }
1462
-        return $contents_with_prefix;
1463
-    }
1464
-
1465
-
1466
-    /**
1467
-     * @deprecated since 4.8.36.rc.001 You should instead use Read::explode_and_get_items_prefixed_with.
1468
-     * Deprecated because its return values were really quite confusing- sometimes it returned
1469
-     * an empty array (when the include string was blank or '*') or sometimes it returned
1470
-     * array('*') (when you provided a model and a model of that kind was found).
1471
-     * Parses the $include_string so we fetch all the field names relating to THIS model
1472
-     * (ie have NO period in them), or for the provided model (ie start with the model
1473
-     * name and then a period).
1474
-     * @param string $include_string @see Read:handle_request_get_all
1475
-     * @param string $model_name
1476
-     * @return array of fields for this model. If $model_name is provided, then
1477
-     *                               the fields for that model, with the model's name removed from each.
1478
-     *                               If $include_string was blank or '*' returns an empty array
1479
-     */
1480
-    public function extractIncludesForThisModel($include_string, $model_name = null)
1481
-    {
1482
-        if (is_array($include_string)) {
1483
-            $include_string = implode(',', $include_string);
1484
-        }
1485
-        if ($include_string === '*' || $include_string === '') {
1486
-            return array();
1487
-        }
1488
-        $includes = explode(',', $include_string);
1489
-        $extracted_fields_to_include = array();
1490
-        if ($model_name) {
1491
-            foreach ($includes as $field_to_include) {
1492
-                $field_to_include = trim($field_to_include);
1493
-                if (strpos($field_to_include, $model_name . '.') === 0) {
1494
-                    // found the model name at the exact start
1495
-                    $field_sans_model_name = str_replace($model_name . '.', '', $field_to_include);
1496
-                    $extracted_fields_to_include[] = $field_sans_model_name;
1497
-                } elseif ($field_to_include == $model_name) {
1498
-                    $extracted_fields_to_include[] = '*';
1499
-                }
1500
-            }
1501
-        } else {
1502
-            // look for ones with no period
1503
-            foreach ($includes as $field_to_include) {
1504
-                $field_to_include = trim($field_to_include);
1505
-                if (strpos($field_to_include, '.') === false
1506
-                    && ! $this->getModelVersionInfo()->isModelNameInThisVersion($field_to_include)
1507
-                ) {
1508
-                    $extracted_fields_to_include[] = $field_to_include;
1509
-                }
1510
-            }
1511
-        }
1512
-        return $extracted_fields_to_include;
1513
-    }
1514
-
1515
-
1516
-    /**
1517
-     * Gets the single item using the model according to the request in the context given, otherwise
1518
-     * returns that it's inaccessible to the current user
1519
-     *
1520
-     * @param EEM_Base $model
1521
-     * @param WP_REST_Request $request
1522
-     * @param null $context
1523
-     * @return array
1524
-     * @throws EE_Error
1525
-     */
1526
-    public function getOneOrReportPermissionError(EEM_Base $model, WP_REST_Request $request, $context = null)
1527
-    {
1528
-        $query_params = array(array($model->primary_key_name() => $request->get_param('id')), 'limit' => 1);
1529
-        if ($model instanceof EEM_Soft_Delete_Base) {
1530
-            $query_params = $model->alter_query_params_so_deleted_and_undeleted_items_included($query_params);
1531
-        }
1532
-        $restricted_query_params = $query_params;
1533
-        $restricted_query_params['caps'] = $context;
1534
-        $this->setDebugInfo('model query params', $restricted_query_params);
1535
-        $model_rows = $model->get_all_wpdb_results($restricted_query_params);
1536
-        if (! empty($model_rows)) {
1537
-            return $this->createEntityFromWpdbResult(
1538
-                $model,
1539
-                reset($model_rows),
1540
-                $request
1541
-            );
1542
-        } else {
1543
-            // ok let's test to see if we WOULD have found it, had we not had restrictions from missing capabilities
1544
-            $lowercase_model_name = strtolower($model->get_this_model_name());
1545
-            if ($model->exists($query_params)) {
1546
-                // you got shafted- it existed but we didn't want to tell you!
1547
-                throw new RestException(
1548
-                    'rest_user_cannot_' . $context,
1549
-                    sprintf(
1550
-                        __('Sorry, you cannot %1$s this %2$s. Missing permissions are: %3$s', 'event_espresso'),
1551
-                        $context,
1552
-                        $lowercase_model_name,
1553
-                        Capabilities::getMissingPermissionsString(
1554
-                            $model,
1555
-                            $context
1556
-                        )
1557
-                    ),
1558
-                    array('status' => 403)
1559
-                );
1560
-            } else {
1561
-                // it's not you. It just doesn't exist
1562
-                throw new RestException(
1563
-                    sprintf('rest_%s_invalid_id', $lowercase_model_name),
1564
-                    sprintf(__('Invalid %s ID.', 'event_espresso'), $lowercase_model_name),
1565
-                    array('status' => 404)
1566
-                );
1567
-            }
1568
-        }
1569
-    }
1570
-
1571
-    /**
1572
-     * Checks that if this content requires a password to be read, that it's been provided and is correct.
1573
-     * @since 4.9.74.p
1574
-     * @param EEM_Base $model
1575
-     * @param $model_row
1576
-     * @param $query_params Adds 'default_where_conditions' => 'minimum' to ensure we don't confuse trashed with
1577
-     *                      password protected.
1578
-     * @param WP_REST_Request $request
1579
-     * @throws EE_Error
1580
-     * @throws InvalidArgumentException
1581
-     * @throws InvalidDataTypeException
1582
-     * @throws InvalidInterfaceException
1583
-     * @throws RestPasswordRequiredException
1584
-     * @throws RestPasswordIncorrectException
1585
-     * @throws \EventEspresso\core\exceptions\ModelConfigurationException
1586
-     * @throws ReflectionException
1587
-     */
1588
-    protected function checkPassword(EEM_Base $model, $model_row, $query_params, WP_REST_Request $request)
1589
-    {
1590
-        $query_params['default_where_conditions'] = 'minimum';
1591
-        // stuff is only "protected" for front-end requests. Elsewhere, you either get full permission to access the object
1592
-        // or you don't.
1593
-        $request_caps = $request->get_param('caps');
1594
-        if (isset($request_caps) && $request_caps !== EEM_Base::caps_read) {
1595
-            return;
1596
-        }
1597
-        // if this entity requires a password, they better give it and it better be right!
1598
-        if ($model->hasPassword()
1599
-            && $model_row[ $model->getPasswordField()->get_qualified_column() ] !== '') {
1600
-            if (empty($request['password'])) {
1601
-                throw new RestPasswordRequiredException();
1602
-            } elseif (!hash_equals(
1603
-                $model_row[ $model->getPasswordField()->get_qualified_column() ],
1604
-                $request['password']
1605
-            )) {
1606
-                throw new RestPasswordIncorrectException();
1607
-            }
1608
-        } // wait! maybe this content is password protected
1609
-        elseif ($model->restrictedByRelatedModelPassword()
1610
-            && $request->get_param('caps') === EEM_Base::caps_read) {
1611
-            $password_supplied = $request->get_param('password');
1612
-            if (empty($password_supplied)) {
1613
-                $query_params['exclude_protected'] = true;
1614
-                if (!$model->exists($query_params)) {
1615
-                    throw new RestPasswordRequiredException();
1616
-                }
1617
-            } else {
1618
-                $query_params[0][ $model->modelChainAndPassword() ] = $password_supplied;
1619
-                if (!$model->exists($query_params)) {
1620
-                    throw new RestPasswordIncorrectException();
1621
-                }
1622
-            }
1623
-        }
1624
-    }
48
+	/**
49
+	 * @var CalculatedModelFields
50
+	 */
51
+	protected $fields_calculator;
52
+
53
+
54
+	/**
55
+	 * Read constructor.
56
+	 * @param CalculatedModelFields $fields_calculator
57
+	 */
58
+	public function __construct(CalculatedModelFields $fields_calculator)
59
+	{
60
+		parent::__construct();
61
+		$this->fields_calculator = $fields_calculator;
62
+	}
63
+
64
+
65
+	/**
66
+	 * Handles requests to get all (or a filtered subset) of entities for a particular model
67
+	 *
68
+	 * @param WP_REST_Request $request
69
+	 * @param string $version
70
+	 * @param string $model_name
71
+	 * @return WP_REST_Response|WP_Error
72
+	 * @throws InvalidArgumentException
73
+	 * @throws InvalidDataTypeException
74
+	 * @throws InvalidInterfaceException
75
+	 */
76
+	public static function handleRequestGetAll(WP_REST_Request $request, $version, $model_name)
77
+	{
78
+		$controller = LoaderFactory::getLoader()->getNew('EventEspresso\core\libraries\rest_api\controllers\model\Read');
79
+		try {
80
+			$controller->setRequestedVersion($version);
81
+			if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
82
+				return $controller->sendResponse(
83
+					new WP_Error(
84
+						'endpoint_parsing_error',
85
+						sprintf(
86
+							__(
87
+								'There is no model for endpoint %s. Please contact event espresso support',
88
+								'event_espresso'
89
+							),
90
+							$model_name
91
+						)
92
+					)
93
+				);
94
+			}
95
+			return $controller->sendResponse(
96
+				$controller->getEntitiesFromModel(
97
+					$controller->getModelVersionInfo()->loadModel($model_name),
98
+					$request
99
+				)
100
+			);
101
+		} catch (Exception $e) {
102
+			return $controller->sendResponse($e);
103
+		}
104
+	}
105
+
106
+
107
+	/**
108
+	 * Prepares and returns schema for any OPTIONS request.
109
+	 *
110
+	 * @param string $version The API endpoint version being used.
111
+	 * @param string $model_name Something like `Event` or `Registration`
112
+	 * @return array
113
+	 * @throws InvalidArgumentException
114
+	 * @throws InvalidDataTypeException
115
+	 * @throws InvalidInterfaceException
116
+	 */
117
+	public static function handleSchemaRequest($version, $model_name)
118
+	{
119
+		$controller = LoaderFactory::getLoader()->getNew('EventEspresso\core\libraries\rest_api\controllers\model\Read');
120
+		try {
121
+			$controller->setRequestedVersion($version);
122
+			if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
123
+				return array();
124
+			}
125
+			// get the model for this version
126
+			$model = $controller->getModelVersionInfo()->loadModel($model_name);
127
+			$model_schema = new JsonModelSchema($model, LoaderFactory::getLoader()->getShared('EventEspresso\core\libraries\rest_api\CalculatedModelFields'));
128
+			return $model_schema->getModelSchemaForRelations(
129
+				$controller->getModelVersionInfo()->relationSettings($model),
130
+				$controller->customizeSchemaForRestResponse(
131
+					$model,
132
+					$model_schema->getModelSchemaForFields(
133
+						$controller->getModelVersionInfo()->fieldsOnModelInThisVersion($model),
134
+						$model_schema->getInitialSchemaStructure()
135
+					)
136
+				)
137
+			);
138
+		} catch (Exception $e) {
139
+			return array();
140
+		}
141
+	}
142
+
143
+
144
+	/**
145
+	 * This loops through each field in the given schema for the model and does the following:
146
+	 * - add any extra fields that are REST API specific and related to existing fields.
147
+	 * - transform default values into the correct format for a REST API response.
148
+	 *
149
+	 * @param EEM_Base $model
150
+	 * @param array    $schema
151
+	 * @return array  The final schema.
152
+	 */
153
+	protected function customizeSchemaForRestResponse(EEM_Base $model, array $schema)
154
+	{
155
+		foreach ($this->getModelVersionInfo()->fieldsOnModelInThisVersion($model) as $field_name => $field) {
156
+			$schema = $this->translateDefaultsForRestResponse(
157
+				$field_name,
158
+				$field,
159
+				$this->maybeAddExtraFieldsToSchema($field_name, $field, $schema)
160
+			);
161
+		}
162
+		return $schema;
163
+	}
164
+
165
+
166
+	/**
167
+	 * This is used to ensure that the 'default' value set in the schema response is formatted correctly for the REST
168
+	 * response.
169
+	 *
170
+	 * @param                      $field_name
171
+	 * @param EE_Model_Field_Base  $field
172
+	 * @param array                $schema
173
+	 * @return array
174
+	 * @throws ObjectDetectedException if a default value has a PHP object, which should never do (and if we
175
+	 * did, let's know about it ASAP, so let the exception bubble up)
176
+	 */
177
+	protected function translateDefaultsForRestResponse($field_name, EE_Model_Field_Base $field, array $schema)
178
+	{
179
+		if (isset($schema['properties'][ $field_name ]['default'])) {
180
+			if (is_array($schema['properties'][ $field_name ]['default'])) {
181
+				foreach ($schema['properties'][ $field_name ]['default'] as $default_key => $default_value) {
182
+					if ($default_key === 'raw') {
183
+						$schema['properties'][ $field_name ]['default'][ $default_key ] =
184
+							ModelDataTranslator::prepareFieldValueForJson(
185
+								$field,
186
+								$default_value,
187
+								$this->getModelVersionInfo()->requestedVersion()
188
+							);
189
+					}
190
+				}
191
+			} else {
192
+				$schema['properties'][ $field_name ]['default'] = ModelDataTranslator::prepareFieldValueForJson(
193
+					$field,
194
+					$schema['properties'][ $field_name ]['default'],
195
+					$this->getModelVersionInfo()->requestedVersion()
196
+				);
197
+			}
198
+		}
199
+		return $schema;
200
+	}
201
+
202
+
203
+	/**
204
+	 * Adds additional fields to the schema
205
+	 * The REST API returns a GMT value field for each datetime field in the resource.  Thus the description about this
206
+	 * needs to be added to the schema.
207
+	 *
208
+	 * @param                      $field_name
209
+	 * @param EE_Model_Field_Base  $field
210
+	 * @param array                $schema
211
+	 * @return array
212
+	 */
213
+	protected function maybeAddExtraFieldsToSchema($field_name, EE_Model_Field_Base $field, array $schema)
214
+	{
215
+		if ($field instanceof EE_Datetime_Field) {
216
+			$schema['properties'][ $field_name . '_gmt' ] = $field->getSchema();
217
+			// modify the description
218
+			$schema['properties'][ $field_name . '_gmt' ]['description'] = sprintf(
219
+				esc_html__('%s - the value for this field is in GMT.', 'event_espresso'),
220
+				wp_specialchars_decode($field->get_nicename(), ENT_QUOTES)
221
+			);
222
+		}
223
+		return $schema;
224
+	}
225
+
226
+
227
+	/**
228
+	 * Used to figure out the route from the request when a `WP_REST_Request` object is not available
229
+	 *
230
+	 * @return string
231
+	 */
232
+	protected function getRouteFromRequest()
233
+	{
234
+		if (isset($GLOBALS['wp'])
235
+			&& $GLOBALS['wp'] instanceof \WP
236
+			&& isset($GLOBALS['wp']->query_vars['rest_route'])
237
+		) {
238
+			return $GLOBALS['wp']->query_vars['rest_route'];
239
+		} else {
240
+			return isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : '/';
241
+		}
242
+	}
243
+
244
+
245
+	/**
246
+	 * Gets a single entity related to the model indicated in the path and its id
247
+	 *
248
+	 * @param WP_REST_Request $request
249
+	 * @param string $version
250
+	 * @param string $model_name
251
+	 * @return WP_REST_Response|WP_Error
252
+	 * @throws InvalidDataTypeException
253
+	 * @throws InvalidInterfaceException
254
+	 * @throws InvalidArgumentException
255
+	 */
256
+	public static function handleRequestGetOne(WP_REST_Request $request, $version, $model_name)
257
+	{
258
+		$controller = LoaderFactory::getLoader()->getNew('EventEspresso\core\libraries\rest_api\controllers\model\Read');
259
+		try {
260
+			$controller->setRequestedVersion($version);
261
+			if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
262
+				return $controller->sendResponse(
263
+					new WP_Error(
264
+						'endpoint_parsing_error',
265
+						sprintf(
266
+							__(
267
+								'There is no model for endpoint %s. Please contact event espresso support',
268
+								'event_espresso'
269
+							),
270
+							$model_name
271
+						)
272
+					)
273
+				);
274
+			}
275
+			return $controller->sendResponse(
276
+				$controller->getEntityFromModel(
277
+					$controller->getModelVersionInfo()->loadModel($model_name),
278
+					$request
279
+				)
280
+			);
281
+		} catch (Exception $e) {
282
+			return $controller->sendResponse($e);
283
+		}
284
+	}
285
+
286
+
287
+	/**
288
+	 * Gets all the related entities (or if its a belongs-to relation just the one)
289
+	 * to the item with the given id
290
+	 *
291
+	 * @param WP_REST_Request $request
292
+	 * @param string $version
293
+	 * @param string $model_name
294
+	 * @param string $related_model_name
295
+	 * @return WP_REST_Response|WP_Error
296
+	 * @throws InvalidDataTypeException
297
+	 * @throws InvalidInterfaceException
298
+	 * @throws InvalidArgumentException
299
+	 */
300
+	public static function handleRequestGetRelated(
301
+		WP_REST_Request $request,
302
+		$version,
303
+		$model_name,
304
+		$related_model_name
305
+	) {
306
+		$controller = LoaderFactory::getLoader()->getNew('EventEspresso\core\libraries\rest_api\controllers\model\Read');
307
+		try {
308
+			$controller->setRequestedVersion($version);
309
+			if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
310
+				return $controller->sendResponse(
311
+					new WP_Error(
312
+						'endpoint_parsing_error',
313
+						sprintf(
314
+							__(
315
+								'There is no model for endpoint %s. Please contact event espresso support',
316
+								'event_espresso'
317
+							),
318
+							$model_name
319
+						)
320
+					)
321
+				);
322
+			}
323
+			$main_model = $controller->getModelVersionInfo()->loadModel($model_name);
324
+			if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($related_model_name)) {
325
+				return $controller->sendResponse(
326
+					new WP_Error(
327
+						'endpoint_parsing_error',
328
+						sprintf(
329
+							__(
330
+								'There is no model for endpoint %s. Please contact event espresso support',
331
+								'event_espresso'
332
+							),
333
+							$related_model_name
334
+						)
335
+					)
336
+				);
337
+			}
338
+			return $controller->sendResponse(
339
+				$controller->getEntitiesFromRelation(
340
+					$request->get_param('id'),
341
+					$main_model->related_settings_for($related_model_name),
342
+					$request
343
+				)
344
+			);
345
+		} catch (Exception $e) {
346
+			return $controller->sendResponse($e);
347
+		}
348
+	}
349
+
350
+
351
+	/**
352
+	 * Gets a collection for the given model and filters
353
+	 *
354
+	 * @param EEM_Base $model
355
+	 * @param WP_REST_Request $request
356
+	 * @return array
357
+	 * @throws EE_Error
358
+	 * @throws InvalidArgumentException
359
+	 * @throws InvalidDataTypeException
360
+	 * @throws InvalidInterfaceException
361
+	 * @throws ReflectionException
362
+	 * @throws RestException
363
+	 */
364
+	public function getEntitiesFromModel($model, $request)
365
+	{
366
+		$query_params = $this->createModelQueryParams($model, $request->get_params());
367
+		if (! Capabilities::currentUserHasPartialAccessTo($model, $query_params['caps'])) {
368
+			$model_name_plural = EEH_Inflector::pluralize_and_lower($model->get_this_model_name());
369
+			throw new RestException(
370
+				sprintf('rest_%s_cannot_list', $model_name_plural),
371
+				sprintf(
372
+					__('Sorry, you are not allowed to list %1$s. Missing permissions: %2$s', 'event_espresso'),
373
+					$model_name_plural,
374
+					Capabilities::getMissingPermissionsString($model, $query_params['caps'])
375
+				),
376
+				array('status' => 403)
377
+			);
378
+		}
379
+		if (! $request->get_header('no_rest_headers')) {
380
+			$this->setHeadersFromQueryParams($model, $query_params);
381
+		}
382
+		/** @type array $results */
383
+		$results = $model->get_all_wpdb_results($query_params);
384
+		$nice_results = array();
385
+		foreach ($results as $result) {
386
+			$nice_results[] =  $this->createEntityFromWpdbResult(
387
+				$model,
388
+				$result,
389
+				$request
390
+			);
391
+		}
392
+		return $nice_results;
393
+	}
394
+
395
+
396
+	/**
397
+	 * Gets the collection for given relation object
398
+	 * The same as Read::get_entities_from_model(), except if the relation
399
+	 * is a HABTM relation, in which case it merges any non-foreign-key fields from
400
+	 * the join-model-object into the results
401
+	 *
402
+	 * @param array $primary_model_query_params query params for finding the item from which
403
+	 *                                                            relations will be based
404
+	 * @param \EE_Model_Relation_Base $relation
405
+	 * @param WP_REST_Request $request
406
+	 * @return array
407
+	 * @throws EE_Error
408
+	 * @throws InvalidArgumentException
409
+	 * @throws InvalidDataTypeException
410
+	 * @throws InvalidInterfaceException
411
+	 * @throws ReflectionException
412
+	 * @throws RestException
413
+	 * @throws \EventEspresso\core\exceptions\ModelConfigurationException
414
+	 */
415
+	protected function getEntitiesFromRelationUsingModelQueryParams($primary_model_query_params, $relation, $request)
416
+	{
417
+		$context = $this->validateContext($request->get_param('caps'));
418
+		$model = $relation->get_this_model();
419
+		$related_model = $relation->get_other_model();
420
+		if (! isset($primary_model_query_params[0])) {
421
+			$primary_model_query_params[0] = array();
422
+		}
423
+		// check if they can access the 1st model object
424
+		$primary_model_query_params = array(
425
+			0       => $primary_model_query_params[0],
426
+			'limit' => 1,
427
+		);
428
+		if ($model instanceof EEM_Soft_Delete_Base) {
429
+			$primary_model_query_params = $model->alter_query_params_so_deleted_and_undeleted_items_included(
430
+				$primary_model_query_params
431
+			);
432
+		}
433
+		$restricted_query_params = $primary_model_query_params;
434
+		$restricted_query_params['caps'] = $context;
435
+		$restricted_query_params['limit'] = 1;
436
+		$this->setDebugInfo('main model query params', $restricted_query_params);
437
+		$this->setDebugInfo('missing caps', Capabilities::getMissingPermissionsString($related_model, $context));
438
+		$primary_model_rows = $model->get_all_wpdb_results($restricted_query_params);
439
+		$primary_model_row = null;
440
+		if (is_array($primary_model_rows)) {
441
+			$primary_model_row = reset($primary_model_rows);
442
+		}
443
+		if (! (
444
+			Capabilities::currentUserHasPartialAccessTo($related_model, $context)
445
+			&& $primary_model_row
446
+		)
447
+		) {
448
+			if ($relation instanceof EE_Belongs_To_Relation) {
449
+				$related_model_name_maybe_plural = strtolower($related_model->get_this_model_name());
450
+			} else {
451
+				$related_model_name_maybe_plural = EEH_Inflector::pluralize_and_lower(
452
+					$related_model->get_this_model_name()
453
+				);
454
+			}
455
+			throw new RestException(
456
+				sprintf('rest_%s_cannot_list', $related_model_name_maybe_plural),
457
+				sprintf(
458
+					__(
459
+						'Sorry, you are not allowed to list %1$s related to %2$s. Missing permissions: %3$s',
460
+						'event_espresso'
461
+					),
462
+					$related_model_name_maybe_plural,
463
+					$relation->get_this_model()->get_this_model_name(),
464
+					implode(
465
+						',',
466
+						array_keys(
467
+							Capabilities::getMissingPermissions($related_model, $context)
468
+						)
469
+					)
470
+				),
471
+				array('status' => 403)
472
+			);
473
+		}
474
+
475
+		$this->checkPassword(
476
+			$model,
477
+			$primary_model_row,
478
+			$restricted_query_params,
479
+			$request
480
+		);
481
+		$query_params = $this->createModelQueryParams($relation->get_other_model(), $request->get_params());
482
+		foreach ($primary_model_query_params[0] as $where_condition_key => $where_condition_value) {
483
+			$query_params[0][ $relation->get_this_model()->get_this_model_name()
484
+							  . '.'
485
+							  . $where_condition_key ] = $where_condition_value;
486
+		}
487
+		$query_params['default_where_conditions'] = 'none';
488
+		$query_params['caps'] = $context;
489
+		if (! $request->get_header('no_rest_headers')) {
490
+			$this->setHeadersFromQueryParams($relation->get_other_model(), $query_params);
491
+		}
492
+		/** @type array $results */
493
+		$results = $relation->get_other_model()->get_all_wpdb_results($query_params);
494
+		$nice_results = array();
495
+		foreach ($results as $result) {
496
+			$nice_result = $this->createEntityFromWpdbResult(
497
+				$relation->get_other_model(),
498
+				$result,
499
+				$request
500
+			);
501
+			if ($relation instanceof \EE_HABTM_Relation) {
502
+				// put the unusual stuff (properties from the HABTM relation) first, and make sure
503
+				// if there are conflicts we prefer the properties from the main model
504
+				$join_model_result = $this->createEntityFromWpdbResult(
505
+					$relation->get_join_model(),
506
+					$result,
507
+					$request
508
+				);
509
+				$joined_result = array_merge($nice_result, $join_model_result);
510
+				// but keep the meta stuff from the main model
511
+				if (isset($nice_result['meta'])) {
512
+					$joined_result['meta'] = $nice_result['meta'];
513
+				}
514
+				$nice_result = $joined_result;
515
+			}
516
+			$nice_results[] = $nice_result;
517
+		}
518
+		if ($relation instanceof EE_Belongs_To_Relation) {
519
+			return array_shift($nice_results);
520
+		} else {
521
+			return $nice_results;
522
+		}
523
+	}
524
+
525
+
526
+	/**
527
+	 * Gets the collection for given relation object
528
+	 * The same as Read::get_entities_from_model(), except if the relation
529
+	 * is a HABTM relation, in which case it merges any non-foreign-key fields from
530
+	 * the join-model-object into the results
531
+	 *
532
+	 * @param string                  $id the ID of the thing we are fetching related stuff from
533
+	 * @param \EE_Model_Relation_Base $relation
534
+	 * @param WP_REST_Request         $request
535
+	 * @return array
536
+	 * @throws EE_Error
537
+	 */
538
+	public function getEntitiesFromRelation($id, $relation, $request)
539
+	{
540
+		if (! $relation->get_this_model()->has_primary_key_field()) {
541
+			throw new EE_Error(
542
+				sprintf(
543
+					__(
544
+					// @codingStandardsIgnoreStart
545
+						'Read::get_entities_from_relation should only be called from a model with a primary key, it was called from %1$s',
546
+						// @codingStandardsIgnoreEnd
547
+						'event_espresso'
548
+					),
549
+					$relation->get_this_model()->get_this_model_name()
550
+				)
551
+			);
552
+		}
553
+		// can we edit that main item?
554
+		// if not, show nothing but an error
555
+		// otherwise, please proceed
556
+		return $this->getEntitiesFromRelationUsingModelQueryParams(
557
+			array(
558
+				array(
559
+					$relation->get_this_model()->primary_key_name() => $id,
560
+				),
561
+			),
562
+			$relation,
563
+			$request
564
+		);
565
+	}
566
+
567
+
568
+	/**
569
+	 * Sets the headers that are based on the model and query params,
570
+	 * like the total records. This should only be called on the original request
571
+	 * from the client, not on subsequent internal
572
+	 *
573
+	 * @param EEM_Base $model
574
+	 * @param array    $query_params
575
+	 * @return void
576
+	 */
577
+	protected function setHeadersFromQueryParams($model, $query_params)
578
+	{
579
+		$this->setDebugInfo('model query params', $query_params);
580
+		$this->setDebugInfo(
581
+			'missing caps',
582
+			Capabilities::getMissingPermissionsString($model, $query_params['caps'])
583
+		);
584
+		// normally the limit to a 2-part array, where the 2nd item is the limit
585
+		if (! isset($query_params['limit'])) {
586
+			$query_params['limit'] = EED_Core_Rest_Api::get_default_query_limit();
587
+		}
588
+		if (is_array($query_params['limit'])) {
589
+			$limit_parts = $query_params['limit'];
590
+		} else {
591
+			$limit_parts = explode(',', $query_params['limit']);
592
+			if (count($limit_parts) == 1) {
593
+				$limit_parts = array(0, $limit_parts[0]);
594
+			}
595
+		}
596
+		// remove the group by and having parts of the query, as those will
597
+		// make the sql query return an array of values, instead of just a single value
598
+		unset($query_params['group_by'], $query_params['having'], $query_params['limit']);
599
+		$count = $model->count($query_params, null, true);
600
+		$pages = $count / $limit_parts[1];
601
+		$this->setResponseHeader('Total', $count, false);
602
+		$this->setResponseHeader('PageSize', $limit_parts[1], false);
603
+		$this->setResponseHeader('TotalPages', ceil($pages), false);
604
+	}
605
+
606
+
607
+	/**
608
+	 * Changes database results into REST API entities
609
+	 *
610
+	 * @param EEM_Base $model
611
+	 * @param array $db_row like results from $wpdb->get_results()
612
+	 * @param WP_REST_Request $rest_request
613
+	 * @param string $deprecated no longer used
614
+	 * @return array ready for being converted into json for sending to client
615
+	 * @throws EE_Error
616
+	 * @throws RestException
617
+	 * @throws InvalidDataTypeException
618
+	 * @throws InvalidInterfaceException
619
+	 * @throws InvalidArgumentException
620
+	 * @throws ReflectionException
621
+	 */
622
+	public function createEntityFromWpdbResult($model, $db_row, $rest_request, $deprecated = null)
623
+	{
624
+		if (! $rest_request instanceof WP_REST_Request) {
625
+			// ok so this was called in the old style, where the 3rd arg was
626
+			// $include, and the 4th arg was $context
627
+			// now setup the request just to avoid fatal errors, although we won't be able
628
+			// to truly make use of it because it's kinda devoid of info
629
+			$rest_request = new WP_REST_Request();
630
+			$rest_request->set_param('include', $rest_request);
631
+			$rest_request->set_param('caps', $deprecated);
632
+		}
633
+		if ($rest_request->get_param('caps') == null) {
634
+			$rest_request->set_param('caps', EEM_Base::caps_read);
635
+		}
636
+		$current_user_full_access_to_entity = $model->currentUserCan(
637
+			EEM_Base::caps_read_admin,
638
+			$model->deduce_fields_n_values_from_cols_n_values($db_row)
639
+		);
640
+		$entity_array = $this->createBareEntityFromWpdbResults($model, $db_row);
641
+		$entity_array = $this->addExtraFields($model, $db_row, $entity_array);
642
+		$entity_array['_links'] = $this->getEntityLinks($model, $db_row, $entity_array);
643
+		// when it's a regular read request for a model with a password and the password wasn't provided
644
+		// remove the password protected fields
645
+		$has_protected_fields = false;
646
+		try {
647
+			$this->checkPassword(
648
+				$model,
649
+				$db_row,
650
+				$model->alter_query_params_to_restrict_by_ID(
651
+					$model->get_index_primary_key_string(
652
+						$model->deduce_fields_n_values_from_cols_n_values($db_row)
653
+					)
654
+				),
655
+				$rest_request
656
+			);
657
+		} catch (RestPasswordRequiredException $e) {
658
+			if ($model->hasPassword()) {
659
+				// just remove protected fields
660
+				$has_protected_fields = true;
661
+				$entity_array = Capabilities::filterOutPasswordProtectedFields(
662
+					$entity_array,
663
+					$model,
664
+					$this->getModelVersionInfo()
665
+				);
666
+			} else {
667
+				// that's a problem. None of this should be accessible if no password was provided
668
+				throw $e;
669
+			}
670
+		}
671
+
672
+		$entity_array['_calculated_fields'] = $this->getEntityCalculations($model, $db_row, $rest_request, $has_protected_fields);
673
+		$entity_array = apply_filters(
674
+			'FHEE__Read__create_entity_from_wpdb_results__entity_before_including_requested_models',
675
+			$entity_array,
676
+			$model,
677
+			$rest_request->get_param('caps'),
678
+			$rest_request,
679
+			$this
680
+		);
681
+		// add an empty protected property for now. If it's still around after we remove everything the request didn't
682
+		// want, we'll populate it then. k?
683
+		$entity_array['_protected'] = array();
684
+		// remove any properties the request didn't want. This way _protected won't bother mentioning them
685
+		$entity_array = $this->includeOnlyRequestedProperties($model, $rest_request, $entity_array);
686
+		$entity_array = $this->includeRequestedModels($model, $rest_request, $entity_array, $db_row, $has_protected_fields);
687
+		// if they still wanted the _protected property, add it.
688
+		if (isset($entity_array['_protected'])) {
689
+			$entity_array = $this->addProtectedProperty($model, $entity_array, $has_protected_fields);
690
+		}
691
+		$entity_array = apply_filters(
692
+			'FHEE__Read__create_entity_from_wpdb_results__entity_before_inaccessible_field_removal',
693
+			$entity_array,
694
+			$model,
695
+			$rest_request->get_param('caps'),
696
+			$rest_request,
697
+			$this
698
+		);
699
+		if (! $current_user_full_access_to_entity) {
700
+			$result_without_inaccessible_fields = Capabilities::filterOutInaccessibleEntityFields(
701
+				$entity_array,
702
+				$model,
703
+				$rest_request->get_param('caps'),
704
+				$this->getModelVersionInfo()
705
+			);
706
+		} else {
707
+			$result_without_inaccessible_fields = $entity_array;
708
+		}
709
+		$this->setDebugInfo(
710
+			'inaccessible fields',
711
+			array_keys(array_diff_key((array) $entity_array, (array) $result_without_inaccessible_fields))
712
+		);
713
+		return apply_filters(
714
+			'FHEE__Read__create_entity_from_wpdb_results__entity_return',
715
+			$result_without_inaccessible_fields,
716
+			$model,
717
+			$rest_request->get_param('caps')
718
+		);
719
+	}
720
+
721
+	/**
722
+	 * Returns an array describing which fields can be protected, and which actually were removed this request
723
+	 * @since 4.9.74.p
724
+	 * @param $model
725
+	 * @param $results_so_far
726
+	 * @param $protected
727
+	 * @return array results
728
+	 */
729
+	protected function addProtectedProperty(EEM_Base $model, $results_so_far, $protected)
730
+	{
731
+		if (! $model->hasPassword() || ! $protected) {
732
+			return $results_so_far;
733
+		}
734
+		$password_field = $model->getPasswordField();
735
+		$all_protected = array_merge(
736
+			array($password_field->get_name()),
737
+			$password_field->protectedFields()
738
+		);
739
+		$fields_included = array_keys($results_so_far);
740
+		$fields_included = array_intersect(
741
+			$all_protected,
742
+			$fields_included
743
+		);
744
+		foreach ($fields_included as $field_name) {
745
+			$results_so_far['_protected'][] = $field_name ;
746
+		}
747
+		return $results_so_far;
748
+	}
749
+
750
+	/**
751
+	 * Creates a REST entity array (JSON object we're going to return in the response, but
752
+	 * for now still a PHP array, but soon enough we'll call json_encode on it, don't worry),
753
+	 * from $wpdb->get_row( $sql, ARRAY_A)
754
+	 *
755
+	 * @param EEM_Base $model
756
+	 * @param array    $db_row
757
+	 * @return array entity mostly ready for converting to JSON and sending in the response
758
+	 */
759
+	protected function createBareEntityFromWpdbResults(EEM_Base $model, $db_row)
760
+	{
761
+		$result = $model->deduce_fields_n_values_from_cols_n_values($db_row);
762
+		$result = array_intersect_key(
763
+			$result,
764
+			$this->getModelVersionInfo()->fieldsOnModelInThisVersion($model)
765
+		);
766
+		// if this is a CPT, we need to set the global $post to it,
767
+		// otherwise shortcodes etc won't work properly while rendering it
768
+		if ($model instanceof \EEM_CPT_Base) {
769
+			$do_chevy_shuffle = true;
770
+		} else {
771
+			$do_chevy_shuffle = false;
772
+		}
773
+		if ($do_chevy_shuffle) {
774
+			global $post;
775
+			$old_post = $post;
776
+			$post = get_post($result[ $model->primary_key_name() ]);
777
+			if (! $post instanceof \WP_Post) {
778
+				// well that's weird, because $result is what we JUST fetched from the database
779
+				throw new RestException(
780
+					'error_fetching_post_from_database_results',
781
+					esc_html__(
782
+						'An item was retrieved from the database but it\'s not a WP_Post like it should be.',
783
+						'event_espresso'
784
+					)
785
+				);
786
+			}
787
+			$model_object_classname = 'EE_' . $model->get_this_model_name();
788
+			$post->{$model_object_classname} = \EE_Registry::instance()->load_class(
789
+				$model_object_classname,
790
+				$result,
791
+				false,
792
+				false
793
+			);
794
+		}
795
+		foreach ($result as $field_name => $field_value) {
796
+			$field_obj = $model->field_settings_for($field_name);
797
+			if ($this->isSubclassOfOne($field_obj, $this->getModelVersionInfo()->fieldsIgnored())) {
798
+				unset($result[ $field_name ]);
799
+			} elseif ($this->isSubclassOfOne(
800
+				$field_obj,
801
+				$this->getModelVersionInfo()->fieldsThatHaveRenderedFormat()
802
+			)
803
+			) {
804
+				$result[ $field_name ] = array(
805
+					'raw'      => $this->prepareFieldObjValueForJson($field_obj, $field_value),
806
+					'rendered' => $this->prepareFieldObjValueForJson($field_obj, $field_value, 'pretty'),
807
+				);
808
+			} elseif ($this->isSubclassOfOne(
809
+				$field_obj,
810
+				$this->getModelVersionInfo()->fieldsThatHavePrettyFormat()
811
+			)
812
+			) {
813
+				$result[ $field_name ] = array(
814
+					'raw'    => $this->prepareFieldObjValueForJson($field_obj, $field_value),
815
+					'pretty' => $this->prepareFieldObjValueForJson($field_obj, $field_value, 'pretty'),
816
+				);
817
+			} elseif ($field_obj instanceof \EE_Datetime_Field) {
818
+				$field_value = $field_obj->prepare_for_set_from_db($field_value);
819
+				// if the value is null, but we're not supposed to permit null, then set to the field's default
820
+				if (is_null($field_value)) {
821
+					$field_value = $field_obj->getDefaultDateTimeObj();
822
+				}
823
+				if (is_null($field_value)) {
824
+					$gmt_date = $local_date = ModelDataTranslator::prepareFieldValuesForJson(
825
+						$field_obj,
826
+						$field_value,
827
+						$this->getModelVersionInfo()->requestedVersion()
828
+					);
829
+				} else {
830
+					$timezone = $field_value->getTimezone();
831
+					EEH_DTT_Helper::setTimezone($field_value, new DateTimeZone('UTC'));
832
+					$gmt_date = ModelDataTranslator::prepareFieldValuesForJson(
833
+						$field_obj,
834
+						$field_value,
835
+						$this->getModelVersionInfo()->requestedVersion()
836
+					);
837
+					EEH_DTT_Helper::setTimezone($field_value, $timezone);
838
+					$local_date = ModelDataTranslator::prepareFieldValuesForJson(
839
+						$field_obj,
840
+						$field_value,
841
+						$this->getModelVersionInfo()->requestedVersion()
842
+					);
843
+				}
844
+				$result[ $field_name . '_gmt' ] = $gmt_date;
845
+				$result[ $field_name ] = $local_date;
846
+			} else {
847
+				$result[ $field_name ] = $this->prepareFieldObjValueForJson($field_obj, $field_value);
848
+			}
849
+		}
850
+		if ($do_chevy_shuffle) {
851
+			$post = $old_post;
852
+		}
853
+		return $result;
854
+	}
855
+
856
+
857
+	/**
858
+	 * Takes a value all the way from the DB representation, to the model object's representation, to the
859
+	 * user-facing PHP representation, to the REST API representation. (Assumes you've already taken from the DB
860
+	 * representation using $field_obj->prepare_for_set_from_db())
861
+	 *
862
+	 * @param EE_Model_Field_Base $field_obj
863
+	 * @param mixed               $value  as it's stored on a model object
864
+	 * @param string              $format valid values are 'normal' (default), 'pretty', 'datetime_obj'
865
+	 * @return mixed
866
+	 * @throws ObjectDetectedException if $value contains a PHP object
867
+	 */
868
+	protected function prepareFieldObjValueForJson(EE_Model_Field_Base $field_obj, $value, $format = 'normal')
869
+	{
870
+		$value = $field_obj->prepare_for_set_from_db($value);
871
+		switch ($format) {
872
+			case 'pretty':
873
+				$value = $field_obj->prepare_for_pretty_echoing($value);
874
+				break;
875
+			case 'normal':
876
+			default:
877
+				$value = $field_obj->prepare_for_get($value);
878
+				break;
879
+		}
880
+		return ModelDataTranslator::prepareFieldValuesForJson(
881
+			$field_obj,
882
+			$value,
883
+			$this->getModelVersionInfo()->requestedVersion()
884
+		);
885
+	}
886
+
887
+
888
+	/**
889
+	 * Adds a few extra fields to the entity response
890
+	 *
891
+	 * @param EEM_Base $model
892
+	 * @param array    $db_row
893
+	 * @param array    $entity_array
894
+	 * @return array modified entity
895
+	 */
896
+	protected function addExtraFields(EEM_Base $model, $db_row, $entity_array)
897
+	{
898
+		if ($model instanceof EEM_CPT_Base) {
899
+			$entity_array['link'] = get_permalink($db_row[ $model->get_primary_key_field()->get_qualified_column() ]);
900
+		}
901
+		return $entity_array;
902
+	}
903
+
904
+
905
+	/**
906
+	 * Gets links we want to add to the response
907
+	 *
908
+	 * @global \WP_REST_Server $wp_rest_server
909
+	 * @param EEM_Base         $model
910
+	 * @param array            $db_row
911
+	 * @param array            $entity_array
912
+	 * @return array the _links item in the entity
913
+	 */
914
+	protected function getEntityLinks($model, $db_row, $entity_array)
915
+	{
916
+		// add basic links
917
+		$links = array();
918
+		if ($model->has_primary_key_field()) {
919
+			$links['self'] = array(
920
+				array(
921
+					'href' => $this->getVersionedLinkTo(
922
+						EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
923
+						. '/'
924
+						. $entity_array[ $model->primary_key_name() ]
925
+					),
926
+				),
927
+			);
928
+		}
929
+		$links['collection'] = array(
930
+			array(
931
+				'href' => $this->getVersionedLinkTo(
932
+					EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
933
+				),
934
+			),
935
+		);
936
+		// add links to related models
937
+		if ($model->has_primary_key_field()) {
938
+			foreach ($this->getModelVersionInfo()->relationSettings($model) as $relation_name => $relation_obj) {
939
+				$related_model_part = Read::getRelatedEntityName($relation_name, $relation_obj);
940
+				$links[ EED_Core_Rest_Api::ee_api_link_namespace . $related_model_part ] = array(
941
+					array(
942
+						'href'   => $this->getVersionedLinkTo(
943
+							EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
944
+							. '/'
945
+							. $entity_array[ $model->primary_key_name() ]
946
+							. '/'
947
+							. $related_model_part
948
+						),
949
+						'single' => $relation_obj instanceof EE_Belongs_To_Relation ? true : false,
950
+					),
951
+				);
952
+			}
953
+		}
954
+		return $links;
955
+	}
956
+
957
+
958
+	/**
959
+	 * Adds the included models indicated in the request to the entity provided
960
+	 *
961
+	 * @param EEM_Base $model
962
+	 * @param WP_REST_Request $rest_request
963
+	 * @param array $entity_array
964
+	 * @param array $db_row
965
+	 * @param boolean $included_items_protected if the original item is password protected, don't include any related models.
966
+	 * @return array the modified entity
967
+	 * @throws RestException
968
+	 */
969
+	protected function includeRequestedModels(
970
+		EEM_Base $model,
971
+		WP_REST_Request $rest_request,
972
+		$entity_array,
973
+		$db_row = array(),
974
+		$included_items_protected = false
975
+	) {
976
+		// if $db_row not included, hope the entity array has what we need
977
+		if (! $db_row) {
978
+			$db_row = $entity_array;
979
+		}
980
+		$relation_settings = $this->getModelVersionInfo()->relationSettings($model);
981
+		foreach ($relation_settings as $relation_name => $relation_obj) {
982
+			$related_fields_to_include = $this->explodeAndGetItemsPrefixedWith(
983
+				$rest_request->get_param('include'),
984
+				$relation_name
985
+			);
986
+			$related_fields_to_calculate = $this->explodeAndGetItemsPrefixedWith(
987
+				$rest_request->get_param('calculate'),
988
+				$relation_name
989
+			);
990
+			// did they specify they wanted to include a related model, or
991
+			// specific fields from a related model?
992
+			// or did they specify to calculate a field from a related model?
993
+			if ($related_fields_to_include || $related_fields_to_calculate) {
994
+				// if so, we should include at least some part of the related model
995
+				$pretend_related_request = new WP_REST_Request();
996
+				$pretend_related_request->set_query_params(
997
+					array(
998
+						'caps'      => $rest_request->get_param('caps'),
999
+						'include'   => $related_fields_to_include,
1000
+						'calculate' => $related_fields_to_calculate,
1001
+						'password' => $rest_request->get_param('password')
1002
+					)
1003
+				);
1004
+				$pretend_related_request->add_header('no_rest_headers', true);
1005
+				$primary_model_query_params = $model->alter_query_params_to_restrict_by_ID(
1006
+					$model->get_index_primary_key_string(
1007
+						$model->deduce_fields_n_values_from_cols_n_values($db_row)
1008
+					)
1009
+				);
1010
+				if (! $included_items_protected) {
1011
+					$related_results = $this->getEntitiesFromRelationUsingModelQueryParams(
1012
+						$primary_model_query_params,
1013
+						$relation_obj,
1014
+						$pretend_related_request
1015
+					);
1016
+				} else {
1017
+					// they're protected, hide them.
1018
+					$related_results = $relation_obj instanceof EE_Belongs_To_Relation ? null : array();
1019
+					$entity_array['_protected'][] = Read::getRelatedEntityName($relation_name, $relation_obj);
1020
+				}
1021
+				if ($related_results instanceof WP_Error) {
1022
+					$related_results = null;
1023
+				}
1024
+				$entity_array[ Read::getRelatedEntityName($relation_name, $relation_obj) ] = $related_results;
1025
+			}
1026
+		}
1027
+		return $entity_array;
1028
+	}
1029
+
1030
+	/**
1031
+	 * If the user has requested only specific properties (including meta properties like _links or _protected)
1032
+	 * remove everything else.
1033
+	 * @since 4.9.74.p
1034
+	 * @param EEM_Base $model
1035
+	 * @param WP_REST_Request $rest_request
1036
+	 * @param $entity_array
1037
+	 * @return array
1038
+	 * @throws EE_Error
1039
+	 */
1040
+	protected function includeOnlyRequestedProperties(
1041
+		EEM_Base $model,
1042
+		WP_REST_Request $rest_request,
1043
+		$entity_array
1044
+	) {
1045
+
1046
+		$includes_for_this_model = $this->explodeAndGetItemsPrefixedWith($rest_request->get_param('include'), '');
1047
+		$includes_for_this_model = $this->removeModelNamesFromArray($includes_for_this_model);
1048
+		// if they passed in * or didn't specify any includes, return everything
1049
+		if (! in_array('*', $includes_for_this_model)
1050
+			&& ! empty($includes_for_this_model)
1051
+		) {
1052
+			if ($model->has_primary_key_field()) {
1053
+				// always include the primary key. ya just gotta know that at least
1054
+				$includes_for_this_model[] = $model->primary_key_name();
1055
+			}
1056
+			if ($this->explodeAndGetItemsPrefixedWith($rest_request->get_param('calculate'), '')) {
1057
+				$includes_for_this_model[] = '_calculated_fields';
1058
+			}
1059
+			$entity_array = array_intersect_key($entity_array, array_flip($includes_for_this_model));
1060
+		}
1061
+		return $entity_array;
1062
+	}
1063
+
1064
+
1065
+	/**
1066
+	 * Returns a new array with all the names of models removed. Eg
1067
+	 * array( 'Event', 'Datetime.*', 'foobar' ) would become array( 'Datetime.*', 'foobar' )
1068
+	 *
1069
+	 * @param array $arr
1070
+	 * @return array
1071
+	 */
1072
+	private function removeModelNamesFromArray($arr)
1073
+	{
1074
+		return array_diff($arr, array_keys(EE_Registry::instance()->non_abstract_db_models));
1075
+	}
1076
+
1077
+
1078
+	/**
1079
+	 * Gets the calculated fields for the response
1080
+	 *
1081
+	 * @param EEM_Base        $model
1082
+	 * @param array           $wpdb_row
1083
+	 * @param WP_REST_Request $rest_request
1084
+	 * @param boolean $row_is_protected whether this row is password protected or not
1085
+	 * @return \stdClass the _calculations item in the entity
1086
+	 * @throws ObjectDetectedException if a default value has a PHP object, which should never do (and if we
1087
+	 * did, let's know about it ASAP, so let the exception bubble up)
1088
+	 */
1089
+	protected function getEntityCalculations($model, $wpdb_row, $rest_request, $row_is_protected = false)
1090
+	{
1091
+		$calculated_fields = $this->explodeAndGetItemsPrefixedWith(
1092
+			$rest_request->get_param('calculate'),
1093
+			''
1094
+		);
1095
+		// note: setting calculate=* doesn't do anything
1096
+		$calculated_fields_to_return = new \stdClass();
1097
+		$protected_fields = array();
1098
+		foreach ($calculated_fields as $field_to_calculate) {
1099
+			try {
1100
+				// it's password protected, so they shouldn't be able to read this. Remove the value
1101
+				$schema = $this->fields_calculator->getJsonSchemaForModel($model);
1102
+				if ($row_is_protected
1103
+					&& isset($schema['properties'][ $field_to_calculate ]['protected'])
1104
+					&& $schema['properties'][ $field_to_calculate ]['protected']) {
1105
+					$calculated_value = null;
1106
+					$protected_fields[] = $field_to_calculate;
1107
+					if ($schema['properties'][ $field_to_calculate ]['type']) {
1108
+						switch ($schema['properties'][ $field_to_calculate ]['type']) {
1109
+							case 'boolean':
1110
+								$calculated_value = false;
1111
+								break;
1112
+							case 'integer':
1113
+								$calculated_value = 0;
1114
+								break;
1115
+							case 'string':
1116
+								$calculated_value = '';
1117
+								break;
1118
+							case 'array':
1119
+								$calculated_value = array();
1120
+								break;
1121
+							case 'object':
1122
+								$calculated_value = new stdClass();
1123
+								break;
1124
+						}
1125
+					}
1126
+				} else {
1127
+					$calculated_value = ModelDataTranslator::prepareFieldValueForJson(
1128
+						null,
1129
+						$this->fields_calculator->retrieveCalculatedFieldValue(
1130
+							$model,
1131
+							$field_to_calculate,
1132
+							$wpdb_row,
1133
+							$rest_request,
1134
+							$this
1135
+						),
1136
+						$this->getModelVersionInfo()->requestedVersion()
1137
+					);
1138
+				}
1139
+				$calculated_fields_to_return->{$field_to_calculate} = $calculated_value;
1140
+			} catch (RestException $e) {
1141
+				// if we don't have permission to read it, just leave it out. but let devs know about the problem
1142
+				$this->setResponseHeader(
1143
+					'Notices-Field-Calculation-Errors['
1144
+					. $e->getStringCode()
1145
+					. ']['
1146
+					. $model->get_this_model_name()
1147
+					. ']['
1148
+					. $field_to_calculate
1149
+					. ']',
1150
+					$e->getMessage(),
1151
+					true
1152
+				);
1153
+			}
1154
+		}
1155
+		$calculated_fields_to_return->_protected = $protected_fields;
1156
+		return $calculated_fields_to_return;
1157
+	}
1158
+
1159
+
1160
+	/**
1161
+	 * Gets the full URL to the resource, taking the requested version into account
1162
+	 *
1163
+	 * @param string $link_part_after_version_and_slash eg "events/10/datetimes"
1164
+	 * @return string url eg "http://mysite.com/wp-json/ee/v4.6/events/10/datetimes"
1165
+	 */
1166
+	public function getVersionedLinkTo($link_part_after_version_and_slash)
1167
+	{
1168
+		return rest_url(
1169
+			EED_Core_Rest_Api::get_versioned_route_to(
1170
+				$link_part_after_version_and_slash,
1171
+				$this->getModelVersionInfo()->requestedVersion()
1172
+			)
1173
+		);
1174
+	}
1175
+
1176
+
1177
+	/**
1178
+	 * Gets the correct lowercase name for the relation in the API according
1179
+	 * to the relation's type
1180
+	 *
1181
+	 * @param string                  $relation_name
1182
+	 * @param \EE_Model_Relation_Base $relation_obj
1183
+	 * @return string
1184
+	 */
1185
+	public static function getRelatedEntityName($relation_name, $relation_obj)
1186
+	{
1187
+		if ($relation_obj instanceof EE_Belongs_To_Relation) {
1188
+			return strtolower($relation_name);
1189
+		} else {
1190
+			return EEH_Inflector::pluralize_and_lower($relation_name);
1191
+		}
1192
+	}
1193
+
1194
+
1195
+	/**
1196
+	 * Gets the one model object with the specified id for the specified model
1197
+	 *
1198
+	 * @param EEM_Base        $model
1199
+	 * @param WP_REST_Request $request
1200
+	 * @return array
1201
+	 */
1202
+	public function getEntityFromModel($model, $request)
1203
+	{
1204
+		$context = $this->validateContext($request->get_param('caps'));
1205
+		return $this->getOneOrReportPermissionError($model, $request, $context);
1206
+	}
1207
+
1208
+
1209
+	/**
1210
+	 * If a context is provided which isn't valid, maybe it was added in a future
1211
+	 * version so just treat it as a default read
1212
+	 *
1213
+	 * @param string $context
1214
+	 * @return string array key of EEM_Base::cap_contexts_to_cap_action_map()
1215
+	 */
1216
+	public function validateContext($context)
1217
+	{
1218
+		if (! $context) {
1219
+			$context = EEM_Base::caps_read;
1220
+		}
1221
+		$valid_contexts = EEM_Base::valid_cap_contexts();
1222
+		if (in_array($context, $valid_contexts)) {
1223
+			return $context;
1224
+		} else {
1225
+			return EEM_Base::caps_read;
1226
+		}
1227
+	}
1228
+
1229
+
1230
+	/**
1231
+	 * Verifies the passed in value is an allowable default where conditions value.
1232
+	 *
1233
+	 * @param $default_query_params
1234
+	 * @return string
1235
+	 */
1236
+	public function validateDefaultQueryParams($default_query_params)
1237
+	{
1238
+		$valid_default_where_conditions_for_api_calls = array(
1239
+			EEM_Base::default_where_conditions_all,
1240
+			EEM_Base::default_where_conditions_minimum_all,
1241
+			EEM_Base::default_where_conditions_minimum_others,
1242
+		);
1243
+		if (! $default_query_params) {
1244
+			$default_query_params = EEM_Base::default_where_conditions_all;
1245
+		}
1246
+		if (in_array(
1247
+			$default_query_params,
1248
+			$valid_default_where_conditions_for_api_calls,
1249
+			true
1250
+		)) {
1251
+			return $default_query_params;
1252
+		} else {
1253
+			return EEM_Base::default_where_conditions_all;
1254
+		}
1255
+	}
1256
+
1257
+
1258
+	/**
1259
+	 * Translates API filter get parameter into model query params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions.
1260
+	 * Note: right now the query parameter keys for fields (and related fields)
1261
+	 * can be left as-is, but it's quite possible this will change someday.
1262
+	 * Also, this method's contents might be candidate for moving to Model_Data_Translator
1263
+	 *
1264
+	 * @param EEM_Base $model
1265
+	 * @param array    $query_parameters  from $_GET parameter @see Read:handle_request_get_all
1266
+	 * @return array model query params (@see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions)
1267
+	 *                                    or FALSE to indicate that absolutely no results should be returned
1268
+	 * @throws EE_Error
1269
+	 * @throws RestException
1270
+	 */
1271
+	public function createModelQueryParams($model, $query_params)
1272
+	{
1273
+		$model_query_params = array();
1274
+		if (isset($query_params['where'])) {
1275
+			$model_query_params[0] = ModelDataTranslator::prepareConditionsQueryParamsForModels(
1276
+				$query_params['where'],
1277
+				$model,
1278
+				$this->getModelVersionInfo()->requestedVersion()
1279
+			);
1280
+		}
1281
+		if (isset($query_params['order_by'])) {
1282
+			$order_by = $query_params['order_by'];
1283
+		} elseif (isset($query_params['orderby'])) {
1284
+			$order_by = $query_params['orderby'];
1285
+		} else {
1286
+			$order_by = null;
1287
+		}
1288
+		if ($order_by !== null) {
1289
+			if (is_array($order_by)) {
1290
+				$order_by = ModelDataTranslator::prepareFieldNamesInArrayKeysFromJson($order_by);
1291
+			} else {
1292
+				// it's a single item
1293
+				$order_by = ModelDataTranslator::prepareFieldNameFromJson($order_by);
1294
+			}
1295
+			$model_query_params['order_by'] = $order_by;
1296
+		}
1297
+		if (isset($query_params['group_by'])) {
1298
+			$group_by = $query_params['group_by'];
1299
+		} elseif (isset($query_params['groupby'])) {
1300
+			$group_by = $query_params['groupby'];
1301
+		} else {
1302
+			$group_by = array_keys($model->get_combined_primary_key_fields());
1303
+		}
1304
+		// make sure they're all real names
1305
+		if (is_array($group_by)) {
1306
+			$group_by = ModelDataTranslator::prepareFieldNamesFromJson($group_by);
1307
+		}
1308
+		if ($group_by !== null) {
1309
+			$model_query_params['group_by'] = $group_by;
1310
+		}
1311
+		if (isset($query_params['having'])) {
1312
+			$model_query_params['having'] = ModelDataTranslator::prepareConditionsQueryParamsForModels(
1313
+				$query_params['having'],
1314
+				$model,
1315
+				$this->getModelVersionInfo()->requestedVersion()
1316
+			);
1317
+		}
1318
+		if (isset($query_params['order'])) {
1319
+			$model_query_params['order'] = $query_params['order'];
1320
+		}
1321
+		if (isset($query_params['mine'])) {
1322
+			$model_query_params = $model->alter_query_params_to_only_include_mine($model_query_params);
1323
+		}
1324
+		if (isset($query_params['limit'])) {
1325
+			// limit should be either a string like '23' or '23,43', or an array with two items in it
1326
+			if (! is_array($query_params['limit'])) {
1327
+				$limit_array = explode(',', (string) $query_params['limit']);
1328
+			} else {
1329
+				$limit_array = $query_params['limit'];
1330
+			}
1331
+			$sanitized_limit = array();
1332
+			foreach ($limit_array as $key => $limit_part) {
1333
+				if ($this->debug_mode && (! is_numeric($limit_part) || count($sanitized_limit) > 2)) {
1334
+					throw new EE_Error(
1335
+						sprintf(
1336
+							__(
1337
+							// @codingStandardsIgnoreStart
1338
+								'An invalid limit filter was provided. It was: %s. If the EE4 JSON REST API weren\'t in debug mode, this message would not appear.',
1339
+								// @codingStandardsIgnoreEnd
1340
+								'event_espresso'
1341
+							),
1342
+							wp_json_encode($query_params['limit'])
1343
+						)
1344
+					);
1345
+				}
1346
+				$sanitized_limit[] = (int) $limit_part;
1347
+			}
1348
+			$model_query_params['limit'] = implode(',', $sanitized_limit);
1349
+		} else {
1350
+			$model_query_params['limit'] = EED_Core_Rest_Api::get_default_query_limit();
1351
+		}
1352
+		if (isset($query_params['caps'])) {
1353
+			$model_query_params['caps'] = $this->validateContext($query_params['caps']);
1354
+		} else {
1355
+			$model_query_params['caps'] = EEM_Base::caps_read;
1356
+		}
1357
+		if (isset($query_params['default_where_conditions'])) {
1358
+			$model_query_params['default_where_conditions'] = $this->validateDefaultQueryParams(
1359
+				$query_params['default_where_conditions']
1360
+			);
1361
+		}
1362
+		// if this is a model protected by a password on another model, exclude the password protected
1363
+		// entities by default. But if they passed in a password, try to show them all. If the password is wrong,
1364
+		// though, they'll get an error (see Read::createEntityFromWpdbResult() which calls Read::checkPassword)
1365
+		if (! $model->hasPassword()
1366
+			&& $model->restrictedByRelatedModelPassword()
1367
+			&& $model_query_params['caps'] === EEM_Base::caps_read) {
1368
+			if (empty($query_params['password'])) {
1369
+				$model_query_params['exclude_protected'] = true;
1370
+			}
1371
+		}
1372
+
1373
+		return apply_filters('FHEE__Read__create_model_query_params', $model_query_params, $query_params, $model);
1374
+	}
1375
+
1376
+
1377
+	/**
1378
+	 * Changes the REST-style query params for use in the models
1379
+	 *
1380
+	 * @deprecated
1381
+	 * @param EEM_Base $model
1382
+	 * @param array    $query_params sub-array from @see EEM_Base::get_all()
1383
+	 * @return array
1384
+	 */
1385
+	public function prepareRestQueryParamsKeyForModels($model, $query_params)
1386
+	{
1387
+		$model_ready_query_params = array();
1388
+		foreach ($query_params as $key => $value) {
1389
+			if (is_array($value)) {
1390
+				$model_ready_query_params[ $key ] = $this->prepareRestQueryParamsKeyForModels($model, $value);
1391
+			} else {
1392
+				$model_ready_query_params[ $key ] = $value;
1393
+			}
1394
+		}
1395
+		return $model_ready_query_params;
1396
+	}
1397
+
1398
+
1399
+	/**
1400
+	 * @deprecated instead use ModelDataTranslator::prepareFieldValuesFromJson()
1401
+	 * @param $model
1402
+	 * @param $query_params
1403
+	 * @return array
1404
+	 */
1405
+	public function prepareRestQueryParamsValuesForModels($model, $query_params)
1406
+	{
1407
+		$model_ready_query_params = array();
1408
+		foreach ($query_params as $key => $value) {
1409
+			if (is_array($value)) {
1410
+				$model_ready_query_params[ $key ] = $this->prepareRestQueryParamsValuesForModels($model, $value);
1411
+			} else {
1412
+				$model_ready_query_params[ $key ] = $value;
1413
+			}
1414
+		}
1415
+		return $model_ready_query_params;
1416
+	}
1417
+
1418
+
1419
+	/**
1420
+	 * Explodes the string on commas, and only returns items with $prefix followed by a period.
1421
+	 * If no prefix is specified, returns items with no period.
1422
+	 *
1423
+	 * @param string|array $string_to_explode eg "jibba,jabba, blah, blah, blah" or array('jibba', 'jabba' )
1424
+	 * @param string       $prefix            "Event" or "foobar"
1425
+	 * @return array $string_to_exploded exploded on COMMAS, and if a prefix was specified
1426
+	 *                                        we only return strings starting with that and a period; if no prefix was
1427
+	 *                                        specified we return all items containing NO periods
1428
+	 */
1429
+	public function explodeAndGetItemsPrefixedWith($string_to_explode, $prefix)
1430
+	{
1431
+		if (is_string($string_to_explode)) {
1432
+			$exploded_contents = explode(',', $string_to_explode);
1433
+		} elseif (is_array($string_to_explode)) {
1434
+			$exploded_contents = $string_to_explode;
1435
+		} else {
1436
+			$exploded_contents = array();
1437
+		}
1438
+		// if the string was empty, we want an empty array
1439
+		$exploded_contents = array_filter($exploded_contents);
1440
+		$contents_with_prefix = array();
1441
+		foreach ($exploded_contents as $item) {
1442
+			$item = trim($item);
1443
+			// if no prefix was provided, so we look for items with no "." in them
1444
+			if (! $prefix) {
1445
+				// does this item have a period?
1446
+				if (strpos($item, '.') === false) {
1447
+					// if not, then its what we're looking for
1448
+					$contents_with_prefix[] = $item;
1449
+				}
1450
+			} elseif (strpos($item, $prefix . '.') === 0) {
1451
+				// this item has the prefix and a period, grab it
1452
+				$contents_with_prefix[] = substr(
1453
+					$item,
1454
+					strpos($item, $prefix . '.') + strlen($prefix . '.')
1455
+				);
1456
+			} elseif ($item === $prefix) {
1457
+				// this item is JUST the prefix
1458
+				// so let's grab everything after, which is a blank string
1459
+				$contents_with_prefix[] = '';
1460
+			}
1461
+		}
1462
+		return $contents_with_prefix;
1463
+	}
1464
+
1465
+
1466
+	/**
1467
+	 * @deprecated since 4.8.36.rc.001 You should instead use Read::explode_and_get_items_prefixed_with.
1468
+	 * Deprecated because its return values were really quite confusing- sometimes it returned
1469
+	 * an empty array (when the include string was blank or '*') or sometimes it returned
1470
+	 * array('*') (when you provided a model and a model of that kind was found).
1471
+	 * Parses the $include_string so we fetch all the field names relating to THIS model
1472
+	 * (ie have NO period in them), or for the provided model (ie start with the model
1473
+	 * name and then a period).
1474
+	 * @param string $include_string @see Read:handle_request_get_all
1475
+	 * @param string $model_name
1476
+	 * @return array of fields for this model. If $model_name is provided, then
1477
+	 *                               the fields for that model, with the model's name removed from each.
1478
+	 *                               If $include_string was blank or '*' returns an empty array
1479
+	 */
1480
+	public function extractIncludesForThisModel($include_string, $model_name = null)
1481
+	{
1482
+		if (is_array($include_string)) {
1483
+			$include_string = implode(',', $include_string);
1484
+		}
1485
+		if ($include_string === '*' || $include_string === '') {
1486
+			return array();
1487
+		}
1488
+		$includes = explode(',', $include_string);
1489
+		$extracted_fields_to_include = array();
1490
+		if ($model_name) {
1491
+			foreach ($includes as $field_to_include) {
1492
+				$field_to_include = trim($field_to_include);
1493
+				if (strpos($field_to_include, $model_name . '.') === 0) {
1494
+					// found the model name at the exact start
1495
+					$field_sans_model_name = str_replace($model_name . '.', '', $field_to_include);
1496
+					$extracted_fields_to_include[] = $field_sans_model_name;
1497
+				} elseif ($field_to_include == $model_name) {
1498
+					$extracted_fields_to_include[] = '*';
1499
+				}
1500
+			}
1501
+		} else {
1502
+			// look for ones with no period
1503
+			foreach ($includes as $field_to_include) {
1504
+				$field_to_include = trim($field_to_include);
1505
+				if (strpos($field_to_include, '.') === false
1506
+					&& ! $this->getModelVersionInfo()->isModelNameInThisVersion($field_to_include)
1507
+				) {
1508
+					$extracted_fields_to_include[] = $field_to_include;
1509
+				}
1510
+			}
1511
+		}
1512
+		return $extracted_fields_to_include;
1513
+	}
1514
+
1515
+
1516
+	/**
1517
+	 * Gets the single item using the model according to the request in the context given, otherwise
1518
+	 * returns that it's inaccessible to the current user
1519
+	 *
1520
+	 * @param EEM_Base $model
1521
+	 * @param WP_REST_Request $request
1522
+	 * @param null $context
1523
+	 * @return array
1524
+	 * @throws EE_Error
1525
+	 */
1526
+	public function getOneOrReportPermissionError(EEM_Base $model, WP_REST_Request $request, $context = null)
1527
+	{
1528
+		$query_params = array(array($model->primary_key_name() => $request->get_param('id')), 'limit' => 1);
1529
+		if ($model instanceof EEM_Soft_Delete_Base) {
1530
+			$query_params = $model->alter_query_params_so_deleted_and_undeleted_items_included($query_params);
1531
+		}
1532
+		$restricted_query_params = $query_params;
1533
+		$restricted_query_params['caps'] = $context;
1534
+		$this->setDebugInfo('model query params', $restricted_query_params);
1535
+		$model_rows = $model->get_all_wpdb_results($restricted_query_params);
1536
+		if (! empty($model_rows)) {
1537
+			return $this->createEntityFromWpdbResult(
1538
+				$model,
1539
+				reset($model_rows),
1540
+				$request
1541
+			);
1542
+		} else {
1543
+			// ok let's test to see if we WOULD have found it, had we not had restrictions from missing capabilities
1544
+			$lowercase_model_name = strtolower($model->get_this_model_name());
1545
+			if ($model->exists($query_params)) {
1546
+				// you got shafted- it existed but we didn't want to tell you!
1547
+				throw new RestException(
1548
+					'rest_user_cannot_' . $context,
1549
+					sprintf(
1550
+						__('Sorry, you cannot %1$s this %2$s. Missing permissions are: %3$s', 'event_espresso'),
1551
+						$context,
1552
+						$lowercase_model_name,
1553
+						Capabilities::getMissingPermissionsString(
1554
+							$model,
1555
+							$context
1556
+						)
1557
+					),
1558
+					array('status' => 403)
1559
+				);
1560
+			} else {
1561
+				// it's not you. It just doesn't exist
1562
+				throw new RestException(
1563
+					sprintf('rest_%s_invalid_id', $lowercase_model_name),
1564
+					sprintf(__('Invalid %s ID.', 'event_espresso'), $lowercase_model_name),
1565
+					array('status' => 404)
1566
+				);
1567
+			}
1568
+		}
1569
+	}
1570
+
1571
+	/**
1572
+	 * Checks that if this content requires a password to be read, that it's been provided and is correct.
1573
+	 * @since 4.9.74.p
1574
+	 * @param EEM_Base $model
1575
+	 * @param $model_row
1576
+	 * @param $query_params Adds 'default_where_conditions' => 'minimum' to ensure we don't confuse trashed with
1577
+	 *                      password protected.
1578
+	 * @param WP_REST_Request $request
1579
+	 * @throws EE_Error
1580
+	 * @throws InvalidArgumentException
1581
+	 * @throws InvalidDataTypeException
1582
+	 * @throws InvalidInterfaceException
1583
+	 * @throws RestPasswordRequiredException
1584
+	 * @throws RestPasswordIncorrectException
1585
+	 * @throws \EventEspresso\core\exceptions\ModelConfigurationException
1586
+	 * @throws ReflectionException
1587
+	 */
1588
+	protected function checkPassword(EEM_Base $model, $model_row, $query_params, WP_REST_Request $request)
1589
+	{
1590
+		$query_params['default_where_conditions'] = 'minimum';
1591
+		// stuff is only "protected" for front-end requests. Elsewhere, you either get full permission to access the object
1592
+		// or you don't.
1593
+		$request_caps = $request->get_param('caps');
1594
+		if (isset($request_caps) && $request_caps !== EEM_Base::caps_read) {
1595
+			return;
1596
+		}
1597
+		// if this entity requires a password, they better give it and it better be right!
1598
+		if ($model->hasPassword()
1599
+			&& $model_row[ $model->getPasswordField()->get_qualified_column() ] !== '') {
1600
+			if (empty($request['password'])) {
1601
+				throw new RestPasswordRequiredException();
1602
+			} elseif (!hash_equals(
1603
+				$model_row[ $model->getPasswordField()->get_qualified_column() ],
1604
+				$request['password']
1605
+			)) {
1606
+				throw new RestPasswordIncorrectException();
1607
+			}
1608
+		} // wait! maybe this content is password protected
1609
+		elseif ($model->restrictedByRelatedModelPassword()
1610
+			&& $request->get_param('caps') === EEM_Base::caps_read) {
1611
+			$password_supplied = $request->get_param('password');
1612
+			if (empty($password_supplied)) {
1613
+				$query_params['exclude_protected'] = true;
1614
+				if (!$model->exists($query_params)) {
1615
+					throw new RestPasswordRequiredException();
1616
+				}
1617
+			} else {
1618
+				$query_params[0][ $model->modelChainAndPassword() ] = $password_supplied;
1619
+				if (!$model->exists($query_params)) {
1620
+					throw new RestPasswordIncorrectException();
1621
+				}
1622
+			}
1623
+		}
1624
+	}
1625 1625
 }
Please login to merge, or discard this patch.
Spacing   +70 added lines, -70 removed lines patch added patch discarded remove patch
@@ -78,7 +78,7 @@  discard block
 block discarded – undo
78 78
         $controller = LoaderFactory::getLoader()->getNew('EventEspresso\core\libraries\rest_api\controllers\model\Read');
79 79
         try {
80 80
             $controller->setRequestedVersion($version);
81
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
81
+            if ( ! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
82 82
                 return $controller->sendResponse(
83 83
                     new WP_Error(
84 84
                         'endpoint_parsing_error',
@@ -119,7 +119,7 @@  discard block
 block discarded – undo
119 119
         $controller = LoaderFactory::getLoader()->getNew('EventEspresso\core\libraries\rest_api\controllers\model\Read');
120 120
         try {
121 121
             $controller->setRequestedVersion($version);
122
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
122
+            if ( ! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
123 123
                 return array();
124 124
             }
125 125
             // get the model for this version
@@ -176,11 +176,11 @@  discard block
 block discarded – undo
176 176
      */
177 177
     protected function translateDefaultsForRestResponse($field_name, EE_Model_Field_Base $field, array $schema)
178 178
     {
179
-        if (isset($schema['properties'][ $field_name ]['default'])) {
180
-            if (is_array($schema['properties'][ $field_name ]['default'])) {
181
-                foreach ($schema['properties'][ $field_name ]['default'] as $default_key => $default_value) {
179
+        if (isset($schema['properties'][$field_name]['default'])) {
180
+            if (is_array($schema['properties'][$field_name]['default'])) {
181
+                foreach ($schema['properties'][$field_name]['default'] as $default_key => $default_value) {
182 182
                     if ($default_key === 'raw') {
183
-                        $schema['properties'][ $field_name ]['default'][ $default_key ] =
183
+                        $schema['properties'][$field_name]['default'][$default_key] =
184 184
                             ModelDataTranslator::prepareFieldValueForJson(
185 185
                                 $field,
186 186
                                 $default_value,
@@ -189,9 +189,9 @@  discard block
 block discarded – undo
189 189
                     }
190 190
                 }
191 191
             } else {
192
-                $schema['properties'][ $field_name ]['default'] = ModelDataTranslator::prepareFieldValueForJson(
192
+                $schema['properties'][$field_name]['default'] = ModelDataTranslator::prepareFieldValueForJson(
193 193
                     $field,
194
-                    $schema['properties'][ $field_name ]['default'],
194
+                    $schema['properties'][$field_name]['default'],
195 195
                     $this->getModelVersionInfo()->requestedVersion()
196 196
                 );
197 197
             }
@@ -213,9 +213,9 @@  discard block
 block discarded – undo
213 213
     protected function maybeAddExtraFieldsToSchema($field_name, EE_Model_Field_Base $field, array $schema)
214 214
     {
215 215
         if ($field instanceof EE_Datetime_Field) {
216
-            $schema['properties'][ $field_name . '_gmt' ] = $field->getSchema();
216
+            $schema['properties'][$field_name.'_gmt'] = $field->getSchema();
217 217
             // modify the description
218
-            $schema['properties'][ $field_name . '_gmt' ]['description'] = sprintf(
218
+            $schema['properties'][$field_name.'_gmt']['description'] = sprintf(
219 219
                 esc_html__('%s - the value for this field is in GMT.', 'event_espresso'),
220 220
                 wp_specialchars_decode($field->get_nicename(), ENT_QUOTES)
221 221
             );
@@ -258,7 +258,7 @@  discard block
 block discarded – undo
258 258
         $controller = LoaderFactory::getLoader()->getNew('EventEspresso\core\libraries\rest_api\controllers\model\Read');
259 259
         try {
260 260
             $controller->setRequestedVersion($version);
261
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
261
+            if ( ! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
262 262
                 return $controller->sendResponse(
263 263
                     new WP_Error(
264 264
                         'endpoint_parsing_error',
@@ -306,7 +306,7 @@  discard block
 block discarded – undo
306 306
         $controller = LoaderFactory::getLoader()->getNew('EventEspresso\core\libraries\rest_api\controllers\model\Read');
307 307
         try {
308 308
             $controller->setRequestedVersion($version);
309
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
309
+            if ( ! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
310 310
                 return $controller->sendResponse(
311 311
                     new WP_Error(
312 312
                         'endpoint_parsing_error',
@@ -321,7 +321,7 @@  discard block
 block discarded – undo
321 321
                 );
322 322
             }
323 323
             $main_model = $controller->getModelVersionInfo()->loadModel($model_name);
324
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($related_model_name)) {
324
+            if ( ! $controller->getModelVersionInfo()->isModelNameInThisVersion($related_model_name)) {
325 325
                 return $controller->sendResponse(
326 326
                     new WP_Error(
327 327
                         'endpoint_parsing_error',
@@ -364,7 +364,7 @@  discard block
 block discarded – undo
364 364
     public function getEntitiesFromModel($model, $request)
365 365
     {
366 366
         $query_params = $this->createModelQueryParams($model, $request->get_params());
367
-        if (! Capabilities::currentUserHasPartialAccessTo($model, $query_params['caps'])) {
367
+        if ( ! Capabilities::currentUserHasPartialAccessTo($model, $query_params['caps'])) {
368 368
             $model_name_plural = EEH_Inflector::pluralize_and_lower($model->get_this_model_name());
369 369
             throw new RestException(
370 370
                 sprintf('rest_%s_cannot_list', $model_name_plural),
@@ -376,14 +376,14 @@  discard block
 block discarded – undo
376 376
                 array('status' => 403)
377 377
             );
378 378
         }
379
-        if (! $request->get_header('no_rest_headers')) {
379
+        if ( ! $request->get_header('no_rest_headers')) {
380 380
             $this->setHeadersFromQueryParams($model, $query_params);
381 381
         }
382 382
         /** @type array $results */
383 383
         $results = $model->get_all_wpdb_results($query_params);
384 384
         $nice_results = array();
385 385
         foreach ($results as $result) {
386
-            $nice_results[] =  $this->createEntityFromWpdbResult(
386
+            $nice_results[] = $this->createEntityFromWpdbResult(
387 387
                 $model,
388 388
                 $result,
389 389
                 $request
@@ -417,7 +417,7 @@  discard block
 block discarded – undo
417 417
         $context = $this->validateContext($request->get_param('caps'));
418 418
         $model = $relation->get_this_model();
419 419
         $related_model = $relation->get_other_model();
420
-        if (! isset($primary_model_query_params[0])) {
420
+        if ( ! isset($primary_model_query_params[0])) {
421 421
             $primary_model_query_params[0] = array();
422 422
         }
423 423
         // check if they can access the 1st model object
@@ -440,7 +440,7 @@  discard block
 block discarded – undo
440 440
         if (is_array($primary_model_rows)) {
441 441
             $primary_model_row = reset($primary_model_rows);
442 442
         }
443
-        if (! (
443
+        if ( ! (
444 444
             Capabilities::currentUserHasPartialAccessTo($related_model, $context)
445 445
             && $primary_model_row
446 446
         )
@@ -480,13 +480,13 @@  discard block
 block discarded – undo
480 480
         );
481 481
         $query_params = $this->createModelQueryParams($relation->get_other_model(), $request->get_params());
482 482
         foreach ($primary_model_query_params[0] as $where_condition_key => $where_condition_value) {
483
-            $query_params[0][ $relation->get_this_model()->get_this_model_name()
483
+            $query_params[0][$relation->get_this_model()->get_this_model_name()
484 484
                               . '.'
485
-                              . $where_condition_key ] = $where_condition_value;
485
+                              . $where_condition_key] = $where_condition_value;
486 486
         }
487 487
         $query_params['default_where_conditions'] = 'none';
488 488
         $query_params['caps'] = $context;
489
-        if (! $request->get_header('no_rest_headers')) {
489
+        if ( ! $request->get_header('no_rest_headers')) {
490 490
             $this->setHeadersFromQueryParams($relation->get_other_model(), $query_params);
491 491
         }
492 492
         /** @type array $results */
@@ -537,7 +537,7 @@  discard block
 block discarded – undo
537 537
      */
538 538
     public function getEntitiesFromRelation($id, $relation, $request)
539 539
     {
540
-        if (! $relation->get_this_model()->has_primary_key_field()) {
540
+        if ( ! $relation->get_this_model()->has_primary_key_field()) {
541 541
             throw new EE_Error(
542 542
                 sprintf(
543 543
                     __(
@@ -582,7 +582,7 @@  discard block
 block discarded – undo
582 582
             Capabilities::getMissingPermissionsString($model, $query_params['caps'])
583 583
         );
584 584
         // normally the limit to a 2-part array, where the 2nd item is the limit
585
-        if (! isset($query_params['limit'])) {
585
+        if ( ! isset($query_params['limit'])) {
586 586
             $query_params['limit'] = EED_Core_Rest_Api::get_default_query_limit();
587 587
         }
588 588
         if (is_array($query_params['limit'])) {
@@ -621,7 +621,7 @@  discard block
 block discarded – undo
621 621
      */
622 622
     public function createEntityFromWpdbResult($model, $db_row, $rest_request, $deprecated = null)
623 623
     {
624
-        if (! $rest_request instanceof WP_REST_Request) {
624
+        if ( ! $rest_request instanceof WP_REST_Request) {
625 625
             // ok so this was called in the old style, where the 3rd arg was
626 626
             // $include, and the 4th arg was $context
627 627
             // now setup the request just to avoid fatal errors, although we won't be able
@@ -696,7 +696,7 @@  discard block
 block discarded – undo
696 696
             $rest_request,
697 697
             $this
698 698
         );
699
-        if (! $current_user_full_access_to_entity) {
699
+        if ( ! $current_user_full_access_to_entity) {
700 700
             $result_without_inaccessible_fields = Capabilities::filterOutInaccessibleEntityFields(
701 701
                 $entity_array,
702 702
                 $model,
@@ -728,7 +728,7 @@  discard block
 block discarded – undo
728 728
      */
729 729
     protected function addProtectedProperty(EEM_Base $model, $results_so_far, $protected)
730 730
     {
731
-        if (! $model->hasPassword() || ! $protected) {
731
+        if ( ! $model->hasPassword() || ! $protected) {
732 732
             return $results_so_far;
733 733
         }
734 734
         $password_field = $model->getPasswordField();
@@ -742,7 +742,7 @@  discard block
 block discarded – undo
742 742
             $fields_included
743 743
         );
744 744
         foreach ($fields_included as $field_name) {
745
-            $results_so_far['_protected'][] = $field_name ;
745
+            $results_so_far['_protected'][] = $field_name;
746 746
         }
747 747
         return $results_so_far;
748 748
     }
@@ -773,8 +773,8 @@  discard block
 block discarded – undo
773 773
         if ($do_chevy_shuffle) {
774 774
             global $post;
775 775
             $old_post = $post;
776
-            $post = get_post($result[ $model->primary_key_name() ]);
777
-            if (! $post instanceof \WP_Post) {
776
+            $post = get_post($result[$model->primary_key_name()]);
777
+            if ( ! $post instanceof \WP_Post) {
778 778
                 // well that's weird, because $result is what we JUST fetched from the database
779 779
                 throw new RestException(
780 780
                     'error_fetching_post_from_database_results',
@@ -784,7 +784,7 @@  discard block
 block discarded – undo
784 784
                     )
785 785
                 );
786 786
             }
787
-            $model_object_classname = 'EE_' . $model->get_this_model_name();
787
+            $model_object_classname = 'EE_'.$model->get_this_model_name();
788 788
             $post->{$model_object_classname} = \EE_Registry::instance()->load_class(
789 789
                 $model_object_classname,
790 790
                 $result,
@@ -795,13 +795,13 @@  discard block
 block discarded – undo
795 795
         foreach ($result as $field_name => $field_value) {
796 796
             $field_obj = $model->field_settings_for($field_name);
797 797
             if ($this->isSubclassOfOne($field_obj, $this->getModelVersionInfo()->fieldsIgnored())) {
798
-                unset($result[ $field_name ]);
798
+                unset($result[$field_name]);
799 799
             } elseif ($this->isSubclassOfOne(
800 800
                 $field_obj,
801 801
                 $this->getModelVersionInfo()->fieldsThatHaveRenderedFormat()
802 802
             )
803 803
             ) {
804
-                $result[ $field_name ] = array(
804
+                $result[$field_name] = array(
805 805
                     'raw'      => $this->prepareFieldObjValueForJson($field_obj, $field_value),
806 806
                     'rendered' => $this->prepareFieldObjValueForJson($field_obj, $field_value, 'pretty'),
807 807
                 );
@@ -810,7 +810,7 @@  discard block
 block discarded – undo
810 810
                 $this->getModelVersionInfo()->fieldsThatHavePrettyFormat()
811 811
             )
812 812
             ) {
813
-                $result[ $field_name ] = array(
813
+                $result[$field_name] = array(
814 814
                     'raw'    => $this->prepareFieldObjValueForJson($field_obj, $field_value),
815 815
                     'pretty' => $this->prepareFieldObjValueForJson($field_obj, $field_value, 'pretty'),
816 816
                 );
@@ -841,10 +841,10 @@  discard block
 block discarded – undo
841 841
                         $this->getModelVersionInfo()->requestedVersion()
842 842
                     );
843 843
                 }
844
-                $result[ $field_name . '_gmt' ] = $gmt_date;
845
-                $result[ $field_name ] = $local_date;
844
+                $result[$field_name.'_gmt'] = $gmt_date;
845
+                $result[$field_name] = $local_date;
846 846
             } else {
847
-                $result[ $field_name ] = $this->prepareFieldObjValueForJson($field_obj, $field_value);
847
+                $result[$field_name] = $this->prepareFieldObjValueForJson($field_obj, $field_value);
848 848
             }
849 849
         }
850 850
         if ($do_chevy_shuffle) {
@@ -896,7 +896,7 @@  discard block
 block discarded – undo
896 896
     protected function addExtraFields(EEM_Base $model, $db_row, $entity_array)
897 897
     {
898 898
         if ($model instanceof EEM_CPT_Base) {
899
-            $entity_array['link'] = get_permalink($db_row[ $model->get_primary_key_field()->get_qualified_column() ]);
899
+            $entity_array['link'] = get_permalink($db_row[$model->get_primary_key_field()->get_qualified_column()]);
900 900
         }
901 901
         return $entity_array;
902 902
     }
@@ -921,7 +921,7 @@  discard block
 block discarded – undo
921 921
                     'href' => $this->getVersionedLinkTo(
922 922
                         EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
923 923
                         . '/'
924
-                        . $entity_array[ $model->primary_key_name() ]
924
+                        . $entity_array[$model->primary_key_name()]
925 925
                     ),
926 926
                 ),
927 927
             );
@@ -937,12 +937,12 @@  discard block
 block discarded – undo
937 937
         if ($model->has_primary_key_field()) {
938 938
             foreach ($this->getModelVersionInfo()->relationSettings($model) as $relation_name => $relation_obj) {
939 939
                 $related_model_part = Read::getRelatedEntityName($relation_name, $relation_obj);
940
-                $links[ EED_Core_Rest_Api::ee_api_link_namespace . $related_model_part ] = array(
940
+                $links[EED_Core_Rest_Api::ee_api_link_namespace.$related_model_part] = array(
941 941
                     array(
942 942
                         'href'   => $this->getVersionedLinkTo(
943 943
                             EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
944 944
                             . '/'
945
-                            . $entity_array[ $model->primary_key_name() ]
945
+                            . $entity_array[$model->primary_key_name()]
946 946
                             . '/'
947 947
                             . $related_model_part
948 948
                         ),
@@ -974,7 +974,7 @@  discard block
 block discarded – undo
974 974
         $included_items_protected = false
975 975
     ) {
976 976
         // if $db_row not included, hope the entity array has what we need
977
-        if (! $db_row) {
977
+        if ( ! $db_row) {
978 978
             $db_row = $entity_array;
979 979
         }
980 980
         $relation_settings = $this->getModelVersionInfo()->relationSettings($model);
@@ -1007,7 +1007,7 @@  discard block
 block discarded – undo
1007 1007
                         $model->deduce_fields_n_values_from_cols_n_values($db_row)
1008 1008
                     )
1009 1009
                 );
1010
-                if (! $included_items_protected) {
1010
+                if ( ! $included_items_protected) {
1011 1011
                     $related_results = $this->getEntitiesFromRelationUsingModelQueryParams(
1012 1012
                         $primary_model_query_params,
1013 1013
                         $relation_obj,
@@ -1021,7 +1021,7 @@  discard block
 block discarded – undo
1021 1021
                 if ($related_results instanceof WP_Error) {
1022 1022
                     $related_results = null;
1023 1023
                 }
1024
-                $entity_array[ Read::getRelatedEntityName($relation_name, $relation_obj) ] = $related_results;
1024
+                $entity_array[Read::getRelatedEntityName($relation_name, $relation_obj)] = $related_results;
1025 1025
             }
1026 1026
         }
1027 1027
         return $entity_array;
@@ -1046,7 +1046,7 @@  discard block
 block discarded – undo
1046 1046
         $includes_for_this_model = $this->explodeAndGetItemsPrefixedWith($rest_request->get_param('include'), '');
1047 1047
         $includes_for_this_model = $this->removeModelNamesFromArray($includes_for_this_model);
1048 1048
         // if they passed in * or didn't specify any includes, return everything
1049
-        if (! in_array('*', $includes_for_this_model)
1049
+        if ( ! in_array('*', $includes_for_this_model)
1050 1050
             && ! empty($includes_for_this_model)
1051 1051
         ) {
1052 1052
             if ($model->has_primary_key_field()) {
@@ -1100,12 +1100,12 @@  discard block
 block discarded – undo
1100 1100
                 // it's password protected, so they shouldn't be able to read this. Remove the value
1101 1101
                 $schema = $this->fields_calculator->getJsonSchemaForModel($model);
1102 1102
                 if ($row_is_protected
1103
-                    && isset($schema['properties'][ $field_to_calculate ]['protected'])
1104
-                    && $schema['properties'][ $field_to_calculate ]['protected']) {
1103
+                    && isset($schema['properties'][$field_to_calculate]['protected'])
1104
+                    && $schema['properties'][$field_to_calculate]['protected']) {
1105 1105
                     $calculated_value = null;
1106 1106
                     $protected_fields[] = $field_to_calculate;
1107
-                    if ($schema['properties'][ $field_to_calculate ]['type']) {
1108
-                        switch ($schema['properties'][ $field_to_calculate ]['type']) {
1107
+                    if ($schema['properties'][$field_to_calculate]['type']) {
1108
+                        switch ($schema['properties'][$field_to_calculate]['type']) {
1109 1109
                             case 'boolean':
1110 1110
                                 $calculated_value = false;
1111 1111
                                 break;
@@ -1215,7 +1215,7 @@  discard block
 block discarded – undo
1215 1215
      */
1216 1216
     public function validateContext($context)
1217 1217
     {
1218
-        if (! $context) {
1218
+        if ( ! $context) {
1219 1219
             $context = EEM_Base::caps_read;
1220 1220
         }
1221 1221
         $valid_contexts = EEM_Base::valid_cap_contexts();
@@ -1240,7 +1240,7 @@  discard block
 block discarded – undo
1240 1240
             EEM_Base::default_where_conditions_minimum_all,
1241 1241
             EEM_Base::default_where_conditions_minimum_others,
1242 1242
         );
1243
-        if (! $default_query_params) {
1243
+        if ( ! $default_query_params) {
1244 1244
             $default_query_params = EEM_Base::default_where_conditions_all;
1245 1245
         }
1246 1246
         if (in_array(
@@ -1323,14 +1323,14 @@  discard block
 block discarded – undo
1323 1323
         }
1324 1324
         if (isset($query_params['limit'])) {
1325 1325
             // limit should be either a string like '23' or '23,43', or an array with two items in it
1326
-            if (! is_array($query_params['limit'])) {
1326
+            if ( ! is_array($query_params['limit'])) {
1327 1327
                 $limit_array = explode(',', (string) $query_params['limit']);
1328 1328
             } else {
1329 1329
                 $limit_array = $query_params['limit'];
1330 1330
             }
1331 1331
             $sanitized_limit = array();
1332 1332
             foreach ($limit_array as $key => $limit_part) {
1333
-                if ($this->debug_mode && (! is_numeric($limit_part) || count($sanitized_limit) > 2)) {
1333
+                if ($this->debug_mode && ( ! is_numeric($limit_part) || count($sanitized_limit) > 2)) {
1334 1334
                     throw new EE_Error(
1335 1335
                         sprintf(
1336 1336
                             __(
@@ -1362,7 +1362,7 @@  discard block
 block discarded – undo
1362 1362
         // if this is a model protected by a password on another model, exclude the password protected
1363 1363
         // entities by default. But if they passed in a password, try to show them all. If the password is wrong,
1364 1364
         // though, they'll get an error (see Read::createEntityFromWpdbResult() which calls Read::checkPassword)
1365
-        if (! $model->hasPassword()
1365
+        if ( ! $model->hasPassword()
1366 1366
             && $model->restrictedByRelatedModelPassword()
1367 1367
             && $model_query_params['caps'] === EEM_Base::caps_read) {
1368 1368
             if (empty($query_params['password'])) {
@@ -1387,9 +1387,9 @@  discard block
 block discarded – undo
1387 1387
         $model_ready_query_params = array();
1388 1388
         foreach ($query_params as $key => $value) {
1389 1389
             if (is_array($value)) {
1390
-                $model_ready_query_params[ $key ] = $this->prepareRestQueryParamsKeyForModels($model, $value);
1390
+                $model_ready_query_params[$key] = $this->prepareRestQueryParamsKeyForModels($model, $value);
1391 1391
             } else {
1392
-                $model_ready_query_params[ $key ] = $value;
1392
+                $model_ready_query_params[$key] = $value;
1393 1393
             }
1394 1394
         }
1395 1395
         return $model_ready_query_params;
@@ -1407,9 +1407,9 @@  discard block
 block discarded – undo
1407 1407
         $model_ready_query_params = array();
1408 1408
         foreach ($query_params as $key => $value) {
1409 1409
             if (is_array($value)) {
1410
-                $model_ready_query_params[ $key ] = $this->prepareRestQueryParamsValuesForModels($model, $value);
1410
+                $model_ready_query_params[$key] = $this->prepareRestQueryParamsValuesForModels($model, $value);
1411 1411
             } else {
1412
-                $model_ready_query_params[ $key ] = $value;
1412
+                $model_ready_query_params[$key] = $value;
1413 1413
             }
1414 1414
         }
1415 1415
         return $model_ready_query_params;
@@ -1441,17 +1441,17 @@  discard block
 block discarded – undo
1441 1441
         foreach ($exploded_contents as $item) {
1442 1442
             $item = trim($item);
1443 1443
             // if no prefix was provided, so we look for items with no "." in them
1444
-            if (! $prefix) {
1444
+            if ( ! $prefix) {
1445 1445
                 // does this item have a period?
1446 1446
                 if (strpos($item, '.') === false) {
1447 1447
                     // if not, then its what we're looking for
1448 1448
                     $contents_with_prefix[] = $item;
1449 1449
                 }
1450
-            } elseif (strpos($item, $prefix . '.') === 0) {
1450
+            } elseif (strpos($item, $prefix.'.') === 0) {
1451 1451
                 // this item has the prefix and a period, grab it
1452 1452
                 $contents_with_prefix[] = substr(
1453 1453
                     $item,
1454
-                    strpos($item, $prefix . '.') + strlen($prefix . '.')
1454
+                    strpos($item, $prefix.'.') + strlen($prefix.'.')
1455 1455
                 );
1456 1456
             } elseif ($item === $prefix) {
1457 1457
                 // this item is JUST the prefix
@@ -1490,9 +1490,9 @@  discard block
 block discarded – undo
1490 1490
         if ($model_name) {
1491 1491
             foreach ($includes as $field_to_include) {
1492 1492
                 $field_to_include = trim($field_to_include);
1493
-                if (strpos($field_to_include, $model_name . '.') === 0) {
1493
+                if (strpos($field_to_include, $model_name.'.') === 0) {
1494 1494
                     // found the model name at the exact start
1495
-                    $field_sans_model_name = str_replace($model_name . '.', '', $field_to_include);
1495
+                    $field_sans_model_name = str_replace($model_name.'.', '', $field_to_include);
1496 1496
                     $extracted_fields_to_include[] = $field_sans_model_name;
1497 1497
                 } elseif ($field_to_include == $model_name) {
1498 1498
                     $extracted_fields_to_include[] = '*';
@@ -1533,7 +1533,7 @@  discard block
 block discarded – undo
1533 1533
         $restricted_query_params['caps'] = $context;
1534 1534
         $this->setDebugInfo('model query params', $restricted_query_params);
1535 1535
         $model_rows = $model->get_all_wpdb_results($restricted_query_params);
1536
-        if (! empty($model_rows)) {
1536
+        if ( ! empty($model_rows)) {
1537 1537
             return $this->createEntityFromWpdbResult(
1538 1538
                 $model,
1539 1539
                 reset($model_rows),
@@ -1545,7 +1545,7 @@  discard block
 block discarded – undo
1545 1545
             if ($model->exists($query_params)) {
1546 1546
                 // you got shafted- it existed but we didn't want to tell you!
1547 1547
                 throw new RestException(
1548
-                    'rest_user_cannot_' . $context,
1548
+                    'rest_user_cannot_'.$context,
1549 1549
                     sprintf(
1550 1550
                         __('Sorry, you cannot %1$s this %2$s. Missing permissions are: %3$s', 'event_espresso'),
1551 1551
                         $context,
@@ -1596,11 +1596,11 @@  discard block
 block discarded – undo
1596 1596
         }
1597 1597
         // if this entity requires a password, they better give it and it better be right!
1598 1598
         if ($model->hasPassword()
1599
-            && $model_row[ $model->getPasswordField()->get_qualified_column() ] !== '') {
1599
+            && $model_row[$model->getPasswordField()->get_qualified_column()] !== '') {
1600 1600
             if (empty($request['password'])) {
1601 1601
                 throw new RestPasswordRequiredException();
1602
-            } elseif (!hash_equals(
1603
-                $model_row[ $model->getPasswordField()->get_qualified_column() ],
1602
+            } elseif ( ! hash_equals(
1603
+                $model_row[$model->getPasswordField()->get_qualified_column()],
1604 1604
                 $request['password']
1605 1605
             )) {
1606 1606
                 throw new RestPasswordIncorrectException();
@@ -1611,12 +1611,12 @@  discard block
 block discarded – undo
1611 1611
             $password_supplied = $request->get_param('password');
1612 1612
             if (empty($password_supplied)) {
1613 1613
                 $query_params['exclude_protected'] = true;
1614
-                if (!$model->exists($query_params)) {
1614
+                if ( ! $model->exists($query_params)) {
1615 1615
                     throw new RestPasswordRequiredException();
1616 1616
                 }
1617 1617
             } else {
1618
-                $query_params[0][ $model->modelChainAndPassword() ] = $password_supplied;
1619
-                if (!$model->exists($query_params)) {
1618
+                $query_params[0][$model->modelChainAndPassword()] = $password_supplied;
1619
+                if ( ! $model->exists($query_params)) {
1620 1620
                     throw new RestPasswordIncorrectException();
1621 1621
                 }
1622 1622
             }
Please login to merge, or discard this patch.