Completed
Branch EDTR/refactor-master (e18acd)
by
unknown
11:03 queued 02:08
created

Read   F

Complexity

Total Complexity 185

Size/Duplication

Total Lines 1628
Duplicated Lines 7.25 %

Coupling/Cohesion

Components 1
Dependencies 23

Importance

Changes 0
Metric Value
dl 118
loc 1628
rs 0.8
c 0
b 0
f 0
wmc 185
lcom 1
cbo 23

35 Methods

Rating   Name   Duplication   Size   Complexity  
A getEntitiesFromRelation() 8 28 2
A __construct() 0 5 1
A translateDefaultsForRestResponse() 14 24 5
A maybeAddExtraFieldsToSchema() 0 12 2
A handleRequestGetAll() 29 29 3
A handleSchemaRequest() 0 25 3
A customizeSchemaForRestResponse() 0 11 2
A getRouteFromRequest() 0 10 5
A handleRequestGetOne() 29 29 3
A handleRequestGetRelated() 0 22 2
A getEntitiesFromModel() 0 30 4
F getEntitiesFromRelationUsingModelQueryParams() 0 109 13
A setHeadersFromQueryParams() 0 28 4
B createEntityFromWpdbResult() 0 98 7
A addProtectedProperty() 0 20 4
C createBareEntityFromWpdbResults() 0 96 12
A prepareFieldObjValueForJson() 0 18 3
A addExtraFields() 0 7 2
A getEntityLinks() 0 42 4
C includeRequestedModels() 0 64 10
A includeOnlyRequestedProperties() 0 21 5
A removeModelNamesFromArray() 0 4 1
C getEntityCalculations() 0 68 12
A getVersionedLinkTo() 0 9 1
A getRelatedEntityName() 0 7 2
A getEntityFromModel() 0 5 1
A validateContext() 0 11 3
A validateDefaultQueryParams() 0 20 3
F createModelQueryParams() 14 104 25
A prepareRestQueryParamsKeyForModels() 12 12 3
A prepareRestQueryParamsValuesForModels() 12 12 3
B explodeAndGetItemsPrefixedWith() 0 35 8
B extractIncludesForThisModel() 0 34 11
A getOneOrReportPermissionError() 0 42 4
C checkPassword() 0 38 12

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Read often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Read, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace EventEspresso\core\libraries\rest_api\controllers\model;
4
5
use DateTimeZone;
6
use DomainException;
7
use EE_HABTM_Relation;
8
use EE_Model_Field_Base;
9
use EE_Model_Relation_Base;
10
use EEH_DTT_Helper;
11
use EEM_Soft_Delete_Base;
12
use EventEspresso\core\exceptions\InvalidDataTypeException;
13
use EventEspresso\core\exceptions\InvalidInterfaceException;
14
use EventEspresso\core\exceptions\ModelConfigurationException;
15
use EventEspresso\core\exceptions\RestPasswordIncorrectException;
16
use EventEspresso\core\exceptions\RestPasswordRequiredException;
17
use EventEspresso\core\exceptions\UnexpectedEntityException;
18
use EventEspresso\core\services\loaders\LoaderFactory;
19
use Exception;
20
use InvalidArgumentException;
21
use ReflectionException;
22
use stdClass;
23
use WP;
24
use WP_Error;
25
use WP_Post;
26
use WP_REST_Request;
27
use EventEspresso\core\libraries\rest_api\Capabilities;
28
use EventEspresso\core\libraries\rest_api\CalculatedModelFields;
29
use EventEspresso\core\libraries\rest_api\RestException;
30
use EventEspresso\core\libraries\rest_api\ModelDataTranslator;
31
use EventEspresso\core\entities\models\JsonModelSchema;
32
use EE_Belongs_To_Relation;
33
use EE_Datetime_Field;
34
use EE_Error;
35
use EE_Registry;
36
use EED_Core_Rest_Api;
37
use EEH_Inflector;
38
use EEM_Base;
39
use EEM_CPT_Base;
40
use WP_REST_Response;
41
use WP_REST_Server;
42
43
/**
44
 * Read controller for models
45
 * Handles requests relating to GET-ting model information
46
 *
47
 * @package               Event Espresso
48
 * @subpackage
49
 * @author                Mike Nelson
50
 */
51
class Read extends Base
52
{
53
54
55
    /**
56
     * @var CalculatedModelFields
57
     */
58
    protected $fields_calculator;
59
60
61
    /**
62
     * Read constructor.
63
     * @param CalculatedModelFields $fields_calculator
64
     */
65
    public function __construct(CalculatedModelFields $fields_calculator)
66
    {
67
        parent::__construct();
68
        $this->fields_calculator = $fields_calculator;
69
    }
70
71
72
    /**
73
     * Handles requests to get all (or a filtered subset) of entities for a particular model
74
     *
75
     * @param WP_REST_Request $request
76
     * @param string $version
77
     * @param string $model_name
78
     * @return WP_REST_Response|WP_Error
79
     * @throws InvalidArgumentException
80
     * @throws InvalidDataTypeException
81
     * @throws InvalidInterfaceException
82
     */
83 View Code Duplication
    public static function handleRequestGetAll(WP_REST_Request $request, $version, $model_name)
84
    {
85
        $controller = LoaderFactory::getLoader()->getNew('EventEspresso\core\libraries\rest_api\controllers\model\Read');
86
        try {
87
            $controller->setRequestedVersion($version);
88
            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
89
                return $controller->sendResponse(
90
                    new WP_Error(
91
                        'endpoint_parsing_error',
92
                        sprintf(
93
                            __(
94
                                'There is no model for endpoint %s. Please contact event espresso support',
95
                                'event_espresso'
96
                            ),
97
                            $model_name
98
                        )
99
                    )
100
                );
101
            }
102
            return $controller->sendResponse(
103
                $controller->getEntitiesFromModel(
104
                    $controller->getModelVersionInfo()->loadModel($model_name),
105
                    $request
106
                )
107
            );
108
        } catch (Exception $e) {
109
            return $controller->sendResponse($e);
110
        }
111
    }
112
113
114
    /**
115
     * Prepares and returns schema for any OPTIONS request.
116
     *
117
     * @param string $version The API endpoint version being used.
118
     * @param string $model_name Something like `Event` or `Registration`
119
     * @return array
120
     * @throws InvalidArgumentException
121
     * @throws InvalidDataTypeException
122
     * @throws InvalidInterfaceException
123
     */
124
    public static function handleSchemaRequest($version, $model_name)
125
    {
126
        $controller = LoaderFactory::getLoader()->getNew('EventEspresso\core\libraries\rest_api\controllers\model\Read');
127
        try {
128
            $controller->setRequestedVersion($version);
129
            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
130
                return array();
131
            }
132
            // get the model for this version
133
            $model = $controller->getModelVersionInfo()->loadModel($model_name);
134
            $model_schema = new JsonModelSchema($model, LoaderFactory::getLoader()->getShared('EventEspresso\core\libraries\rest_api\CalculatedModelFields'));
135
            return $model_schema->getModelSchemaForRelations(
136
                $controller->getModelVersionInfo()->relationSettings($model),
137
                $controller->customizeSchemaForRestResponse(
138
                    $model,
139
                    $model_schema->getModelSchemaForFields(
140
                        $controller->getModelVersionInfo()->fieldsOnModelInThisVersion($model),
141
                        $model_schema->getInitialSchemaStructure()
142
                    )
143
                )
144
            );
145
        } catch (Exception $e) {
146
            return array();
147
        }
148
    }
149
150
151
    /**
152
     * This loops through each field in the given schema for the model and does the following:
153
     * - add any extra fields that are REST API specific and related to existing fields.
154
     * - transform default values into the correct format for a REST API response.
155
     *
156
     * @param EEM_Base $model
157
     * @param array    $schema
158
     * @return array  The final schema.
159
     * @throws EE_Error
160
     */
161
    public function customizeSchemaForRestResponse(EEM_Base $model, array $schema)
162
    {
163
        foreach ($this->getModelVersionInfo()->fieldsOnModelInThisVersion($model) as $field_name => $field) {
164
            $schema = $this->translateDefaultsForRestResponse(
165
                $field_name,
166
                $field,
167
                $this->maybeAddExtraFieldsToSchema($field_name, $field, $schema)
168
            );
169
        }
170
        return $schema;
171
    }
172
173
174
    /**
175
     * This is used to ensure that the 'default' value set in the schema response is formatted correctly for the REST
176
     * response.
177
     *
178
     * @param                      $field_name
179
     * @param EE_Model_Field_Base  $field
180
     * @param array                $schema
181
     * @return array
182
     * @throws RestException if a default value has a PHP object, which should never do (and if we
183
     * @throws EE_Error
184
     * did, let's know about it ASAP, so let the exception bubble up)
185
     */
186
    protected function translateDefaultsForRestResponse($field_name, EE_Model_Field_Base $field, array $schema)
187
    {
188
        if (isset($schema['properties'][ $field_name ]['default'])) {
189
            if (is_array($schema['properties'][ $field_name ]['default'])) {
190
                foreach ($schema['properties'][ $field_name ]['default'] as $default_key => $default_value) {
191 View Code Duplication
                    if ($default_key === 'raw') {
192
                        $schema['properties'][ $field_name ]['default'][ $default_key ] =
193
                            ModelDataTranslator::prepareFieldValueForJson(
194
                                $field,
195
                                $default_value,
196
                                $this->getModelVersionInfo()->requestedVersion()
197
                            );
198
                    }
199
                }
200 View Code Duplication
            } else {
201
                $schema['properties'][ $field_name ]['default'] = ModelDataTranslator::prepareFieldValueForJson(
202
                    $field,
203
                    $schema['properties'][ $field_name ]['default'],
204
                    $this->getModelVersionInfo()->requestedVersion()
205
                );
206
            }
207
        }
208
        return $schema;
209
    }
210
211
212
    /**
213
     * Adds additional fields to the schema
214
     * The REST API returns a GMT value field for each datetime field in the resource.  Thus the description about this
215
     * needs to be added to the schema.
216
     *
217
     * @param                      $field_name
218
     * @param EE_Model_Field_Base  $field
219
     * @param array                $schema
220
     * @return array
221
     */
222
    protected function maybeAddExtraFieldsToSchema($field_name, EE_Model_Field_Base $field, array $schema)
223
    {
224
        if ($field instanceof EE_Datetime_Field) {
225
            $schema['properties'][ $field_name . '_gmt' ] = $field->getSchema();
226
            // modify the description
227
            $schema['properties'][ $field_name . '_gmt' ]['description'] = sprintf(
228
                esc_html__('%s - the value for this field is in GMT.', 'event_espresso'),
229
                wp_specialchars_decode($field->get_nicename(), ENT_QUOTES)
230
            );
231
        }
232
        return $schema;
233
    }
234
235
236
    /**
237
     * Used to figure out the route from the request when a `WP_REST_Request` object is not available
238
     *
239
     * @return string
240
     */
241
    protected function getRouteFromRequest()
242
    {
243
        if (isset($GLOBALS['wp'])
244
            && $GLOBALS['wp'] instanceof WP
0 ignored issues
show
Bug introduced by
The class WP does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
245
            && isset($GLOBALS['wp']->query_vars['rest_route'])
246
        ) {
247
            return $GLOBALS['wp']->query_vars['rest_route'];
248
        }
249
        return isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : '/';
250
    }
251
252
253
    /**
254
     * Gets a single entity related to the model indicated in the path and its id
255
     *
256
     * @param WP_REST_Request $request
257
     * @param string $version
258
     * @param string $model_name
259
     * @return WP_REST_Response|WP_Error
260
     * @throws InvalidDataTypeException
261
     * @throws InvalidInterfaceException
262
     * @throws InvalidArgumentException
263
     */
264 View Code Duplication
    public static function handleRequestGetOne(WP_REST_Request $request, $version, $model_name)
265
    {
266
        $controller = LoaderFactory::getLoader()->getNew('EventEspresso\core\libraries\rest_api\controllers\model\Read');
267
        try {
268
            $controller->setRequestedVersion($version);
269
            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
270
                return $controller->sendResponse(
271
                    new WP_Error(
272
                        'endpoint_parsing_error',
273
                        sprintf(
274
                            __(
275
                                'There is no model for endpoint %s. Please contact event espresso support',
276
                                'event_espresso'
277
                            ),
278
                            $model_name
279
                        )
280
                    )
281
                );
282
            }
283
            return $controller->sendResponse(
284
                $controller->getEntityFromModel(
285
                    $controller->getModelVersionInfo()->loadModel($model_name),
286
                    $request
287
                )
288
            );
289
        } catch (Exception $e) {
290
            return $controller->sendResponse($e);
291
        }
292
    }
293
294
295
    /**
296
     * Gets all the related entities (or if its a belongs-to relation just the one)
297
     * to the item with the given id
298
     *
299
     * @param WP_REST_Request $request
300
     * @param string $version
301
     * @param string $model_name
302
     * @param string $related_model_name
303
     * @return WP_REST_Response|WP_Error
304
     * @throws InvalidDataTypeException
305
     * @throws InvalidInterfaceException
306
     * @throws InvalidArgumentException
307
     */
308
    public static function handleRequestGetRelated(
309
        WP_REST_Request $request,
310
        $version,
311
        $model_name,
312
        $related_model_name
313
    ) {
314
        $controller = LoaderFactory::getLoader()->getNew('EventEspresso\core\libraries\rest_api\controllers\model\Read');
315
        try {
316
            $controller->setRequestedVersion($version);
317
            $main_model = $controller->validateModel($model_name);
318
            $controller->validateModel($related_model_name);
319
            return $controller->sendResponse(
320
                $controller->getEntitiesFromRelation(
321
                    $request->get_param('id'),
322
                    $main_model->related_settings_for($related_model_name),
323
                    $request
324
                )
325
            );
326
        } catch (Exception $e) {
327
            return $controller->sendResponse($e);
328
        }
329
    }
330
331
332
    /**
333
     * Gets a collection for the given model and filters
334
     *
335
     * @param EEM_Base        $model
336
     * @param WP_REST_Request $request
337
     * @return array
338
     * @throws DomainException
339
     * @throws EE_Error
340
     * @throws InvalidArgumentException
341
     * @throws InvalidDataTypeException
342
     * @throws InvalidInterfaceException
343
     * @throws ModelConfigurationException
344
     * @throws ReflectionException
345
     * @throws RestException
346
     * @throws RestPasswordIncorrectException
347
     * @throws RestPasswordRequiredException
348
     * @throws UnexpectedEntityException
349
     */
350
    public function getEntitiesFromModel($model, $request)
351
    {
352
        $query_params = $this->createModelQueryParams($model, $request->get_params());
353
        if (! Capabilities::currentUserHasPartialAccessTo($model, $query_params['caps'])) {
354
            $model_name_plural = EEH_Inflector::pluralize_and_lower($model->get_this_model_name());
355
            throw new RestException(
356
                sprintf('rest_%s_cannot_list', $model_name_plural),
357
                sprintf(
358
                    __('Sorry, you are not allowed to list %1$s. Missing permissions: %2$s', 'event_espresso'),
359
                    $model_name_plural,
360
                    Capabilities::getMissingPermissionsString($model, $query_params['caps'])
361
                ),
362
                array('status' => 403)
363
            );
364
        }
365
        if (! $request->get_header('no_rest_headers')) {
366
            $this->setHeadersFromQueryParams($model, $query_params);
367
        }
368
        /** @type array $results */
369
        $results = $model->get_all_wpdb_results($query_params);
370
        $nice_results = array();
371
        foreach ($results as $result) {
372
            $nice_results[] =  $this->createEntityFromWpdbResult(
373
                $model,
374
                $result,
375
                $request
376
            );
377
        }
378
        return $nice_results;
379
    }
380
381
382
    /**
383
     * Gets the collection for given relation object
384
     * The same as Read::get_entities_from_model(), except if the relation
385
     * is a HABTM relation, in which case it merges any non-foreign-key fields from
386
     * the join-model-object into the results
387
     *
388
     * @param array                  $primary_model_query_params  query params for finding the item from which
389
     *                                                            relations will be based
390
     * @param EE_Model_Relation_Base $relation
391
     * @param WP_REST_Request        $request
392
     * @return array
393
     * @throws DomainException
394
     * @throws EE_Error
395
     * @throws InvalidArgumentException
396
     * @throws InvalidDataTypeException
397
     * @throws InvalidInterfaceException
398
     * @throws ModelConfigurationException
399
     * @throws ReflectionException
400
     * @throws RestException
401
     * @throws RestPasswordIncorrectException
402
     * @throws RestPasswordRequiredException
403
     * @throws UnexpectedEntityException
404
     */
405
    protected function getEntitiesFromRelationUsingModelQueryParams($primary_model_query_params, $relation, $request)
406
    {
407
        $context = $this->validateContext($request->get_param('caps'));
408
        $model = $relation->get_this_model();
409
        $related_model = $relation->get_other_model();
410
        if (! isset($primary_model_query_params[0])) {
411
            $primary_model_query_params[0] = array();
412
        }
413
        // check if they can access the 1st model object
414
        $primary_model_query_params = array(
415
            0       => $primary_model_query_params[0],
416
            'limit' => 1,
417
        );
418
        if ($model instanceof EEM_Soft_Delete_Base) {
419
            $primary_model_query_params = $model->alter_query_params_so_deleted_and_undeleted_items_included(
420
                $primary_model_query_params
421
            );
422
        }
423
        $restricted_query_params = $primary_model_query_params;
424
        $restricted_query_params['caps'] = $context;
425
        $restricted_query_params['limit'] = 1;
426
        $this->setDebugInfo('main model query params', $restricted_query_params);
427
        $this->setDebugInfo('missing caps', Capabilities::getMissingPermissionsString($related_model, $context));
428
        $primary_model_rows = $model->get_all_wpdb_results($restricted_query_params);
429
        $primary_model_row = null;
430
        if (is_array($primary_model_rows)) {
431
            $primary_model_row = reset($primary_model_rows);
432
        }
433
        if (! (
434
            $primary_model_row
435
            && Capabilities::currentUserHasPartialAccessTo($related_model, $context)
436
        )
437
        ) {
438
            if ($relation instanceof EE_Belongs_To_Relation) {
439
                $related_model_name_maybe_plural = strtolower($related_model->get_this_model_name());
440
            } else {
441
                $related_model_name_maybe_plural = EEH_Inflector::pluralize_and_lower(
442
                    $related_model->get_this_model_name()
443
                );
444
            }
445
            throw new RestException(
446
                sprintf('rest_%s_cannot_list', $related_model_name_maybe_plural),
447
                sprintf(
448
                    __(
449
                        'Sorry, you are not allowed to list %1$s related to %2$s. Missing permissions: %3$s',
450
                        'event_espresso'
451
                    ),
452
                    $related_model_name_maybe_plural,
453
                    $relation->get_this_model()->get_this_model_name(),
454
                    implode(
455
                        ',',
456
                        array_keys(
457
                            Capabilities::getMissingPermissions($related_model, $context)
458
                        )
459
                    )
460
                ),
461
                array('status' => 403)
462
            );
463
        }
464
465
        $this->checkPassword(
466
            $model,
467
            $primary_model_row,
468
            $restricted_query_params,
469
            $request
470
        );
471
        $query_params = $this->createModelQueryParams($relation->get_other_model(), $request->get_params());
0 ignored issues
show
Bug introduced by
It seems like $relation->get_other_model() can be null; however, createModelQueryParams() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
472
        foreach ($primary_model_query_params[0] as $where_condition_key => $where_condition_value) {
473
            $query_params[0][ $relation->get_this_model()->get_this_model_name()
474
                              . '.'
475
                              . $where_condition_key ] = $where_condition_value;
476
        }
477
        $query_params['default_where_conditions'] = 'none';
478
        $query_params['caps'] = $context;
479
        if (! $request->get_header('no_rest_headers')) {
480
            $this->setHeadersFromQueryParams($relation->get_other_model(), $query_params);
0 ignored issues
show
Bug introduced by
It seems like $relation->get_other_model() can be null; however, setHeadersFromQueryParams() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
481
        }
482
        /** @type array $results */
483
        $results = $relation->get_other_model()->get_all_wpdb_results($query_params);
484
        $nice_results = array();
485
        foreach ($results as $result) {
486
            $nice_result = $this->createEntityFromWpdbResult(
487
                $relation->get_other_model(),
0 ignored issues
show
Bug introduced by
It seems like $relation->get_other_model() can be null; however, createEntityFromWpdbResult() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
488
                $result,
489
                $request
490
            );
491
            if ($relation instanceof EE_HABTM_Relation) {
492
                // put the unusual stuff (properties from the HABTM relation) first, and make sure
493
                // if there are conflicts we prefer the properties from the main model
494
                $join_model_result = $this->createEntityFromWpdbResult(
495
                    $relation->get_join_model(),
0 ignored issues
show
Bug introduced by
It seems like $relation->get_join_model() can be null; however, createEntityFromWpdbResult() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
496
                    $result,
497
                    $request
498
                );
499
                $joined_result = array_merge($join_model_result, $nice_result);
500
                // but keep the meta stuff from the main model
501
                if (isset($nice_result['meta'])) {
502
                    $joined_result['meta'] = $nice_result['meta'];
503
                }
504
                $nice_result = $joined_result;
505
            }
506
            $nice_results[] = $nice_result;
507
        }
508
        if ($relation instanceof EE_Belongs_To_Relation) {
509
            return array_shift($nice_results);
510
        } else {
511
            return $nice_results;
512
        }
513
    }
514
515
516
    /**
517
     * Gets the collection for given relation object
518
     * The same as Read::get_entities_from_model(), except if the relation
519
     * is a HABTM relation, in which case it merges any non-foreign-key fields from
520
     * the join-model-object into the results
521
     *
522
     * @param string                 $id the ID of the thing we are fetching related stuff from
523
     * @param EE_Model_Relation_Base $relation
524
     * @param WP_REST_Request        $request
525
     * @return array
526
     * @throws DomainException
527
     * @throws EE_Error
528
     * @throws InvalidArgumentException
529
     * @throws InvalidDataTypeException
530
     * @throws InvalidInterfaceException
531
     * @throws ModelConfigurationException
532
     * @throws ReflectionException
533
     * @throws RestException
534
     * @throws RestPasswordIncorrectException
535
     * @throws RestPasswordRequiredException
536
     * @throws UnexpectedEntityException
537
     */
538
    public function getEntitiesFromRelation($id, $relation, $request)
539
    {
540 View Code Duplication
        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
     * @throws EE_Error
577
     */
578
    protected function setHeadersFromQueryParams($model, $query_params)
579
    {
580
        $this->setDebugInfo('model query params', $query_params);
581
        $this->setDebugInfo(
582
            'missing caps',
583
            Capabilities::getMissingPermissionsString($model, $query_params['caps'])
584
        );
585
        // normally the limit to a 2-part array, where the 2nd item is the limit
586
        if (! isset($query_params['limit'])) {
587
            $query_params['limit'] = EED_Core_Rest_Api::get_default_query_limit();
588
        }
589
        if (is_array($query_params['limit'])) {
590
            $limit_parts = $query_params['limit'];
591
        } else {
592
            $limit_parts = explode(',', $query_params['limit']);
593
            if (count($limit_parts) === 1) {
594
                $limit_parts = array(0, $limit_parts[0]);
595
            }
596
        }
597
        // remove the group by and having parts of the query, as those will
598
        // make the sql query return an array of values, instead of just a single value
599
        unset($query_params['group_by'], $query_params['having'], $query_params['limit']);
600
        $count = $model->count($query_params, null, true);
601
        $pages = $count / $limit_parts[1];
602
        $this->setResponseHeader('Total', $count, false);
603
        $this->setResponseHeader('PageSize', $limit_parts[1], false);
604
        $this->setResponseHeader('TotalPages', ceil($pages), false);
605
    }
606
607
608
    /**
609
     * Changes database results into REST API entities
610
     *
611
     * @param EEM_Base        $model
612
     * @param array           $db_row     like results from $wpdb->get_results()
613
     * @param WP_REST_Request $rest_request
614
     * @param string          $deprecated no longer used
615
     * @return array ready for being converted into json for sending to client
616
     * @throws EE_Error
617
     * @throws InvalidArgumentException
618
     * @throws InvalidDataTypeException
619
     * @throws InvalidInterfaceException
620
     * @throws ReflectionException
621
     * @throws RestException
622
     * @throws RestPasswordIncorrectException
623
     * @throws RestPasswordRequiredException
624
     * @throws ModelConfigurationException
625
     * @throws UnexpectedEntityException
626
     * @throws DomainException
627
     */
628
    public function createEntityFromWpdbResult($model, $db_row, $rest_request, $deprecated = null)
629
    {
630
        if (! $rest_request instanceof WP_REST_Request) {
0 ignored issues
show
Bug introduced by
The class WP_REST_Request does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
631
            // ok so this was called in the old style, where the 3rd arg was
632
            // $include, and the 4th arg was $context
633
            // now setup the request just to avoid fatal errors, although we won't be able
634
            // to truly make use of it because it's kinda devoid of info
635
            $rest_request = new WP_REST_Request();
636
            $rest_request->set_param('include', $rest_request);
637
            $rest_request->set_param('caps', $deprecated);
638
        }
639
        if ($rest_request->get_param('caps') === null) {
640
            $rest_request->set_param('caps', EEM_Base::caps_read);
641
        }
642
        $current_user_full_access_to_entity = $model->currentUserCan(
643
            EEM_Base::caps_read_admin,
644
            $model->deduce_fields_n_values_from_cols_n_values($db_row)
645
        );
646
        $entity_array = $this->createBareEntityFromWpdbResults($model, $db_row);
647
        $entity_array = $this->addExtraFields($model, $db_row, $entity_array);
648
        $entity_array['_links'] = $this->getEntityLinks($model, $db_row, $entity_array);
649
        // when it's a regular read request for a model with a password and the password wasn't provided
650
        // remove the password protected fields
651
        $has_protected_fields = false;
652
        try {
653
            $this->checkPassword(
654
                $model,
655
                $db_row,
656
                $model->alter_query_params_to_restrict_by_ID(
657
                    $model->get_index_primary_key_string(
658
                        $model->deduce_fields_n_values_from_cols_n_values($db_row)
659
                    )
660
                ),
661
                $rest_request
662
            );
663
        } catch (RestPasswordRequiredException $e) {
664
            if ($model->hasPassword()) {
665
                // just remove protected fields
666
                $has_protected_fields = true;
667
                $entity_array = Capabilities::filterOutPasswordProtectedFields(
668
                    $entity_array,
669
                    $model,
670
                    $this->getModelVersionInfo()
671
                );
672
            } else {
673
                // that's a problem. None of this should be accessible if no password was provided
674
                throw $e;
675
            }
676
        }
677
678
        $entity_array['_calculated_fields'] = $this->getEntityCalculations($model, $db_row, $rest_request, $has_protected_fields);
679
        $entity_array = apply_filters(
680
            'FHEE__Read__create_entity_from_wpdb_results__entity_before_including_requested_models',
681
            $entity_array,
682
            $model,
683
            $rest_request->get_param('caps'),
684
            $rest_request,
685
            $this
686
        );
687
        // add an empty protected property for now. If it's still around after we remove everything the request didn't
688
        // want, we'll populate it then. k?
689
        $entity_array['_protected'] = array();
690
        // remove any properties the request didn't want. This way _protected won't bother mentioning them
691
        $entity_array = $this->includeOnlyRequestedProperties($model, $rest_request, $entity_array);
692
        $entity_array = $this->includeRequestedModels($model, $rest_request, $entity_array, $db_row, $has_protected_fields);
693
        // if they still wanted the _protected property, add it.
694
        if (isset($entity_array['_protected'])) {
695
            $entity_array = $this->addProtectedProperty($model, $entity_array, $has_protected_fields);
696
        }
697
        $entity_array = apply_filters(
698
            'FHEE__Read__create_entity_from_wpdb_results__entity_before_inaccessible_field_removal',
699
            $entity_array,
700
            $model,
701
            $rest_request->get_param('caps'),
702
            $rest_request,
703
            $this
704
        );
705
        if (! $current_user_full_access_to_entity) {
706
            $result_without_inaccessible_fields = Capabilities::filterOutInaccessibleEntityFields(
707
                $entity_array,
708
                $model,
709
                $rest_request->get_param('caps'),
710
                $this->getModelVersionInfo()
711
            );
712
        } else {
713
            $result_without_inaccessible_fields = $entity_array;
714
        }
715
        $this->setDebugInfo(
716
            'inaccessible fields',
717
            array_keys(array_diff_key((array) $entity_array, (array) $result_without_inaccessible_fields))
718
        );
719
        return apply_filters(
720
            'FHEE__Read__create_entity_from_wpdb_results__entity_return',
721
            $result_without_inaccessible_fields,
722
            $model,
723
            $rest_request->get_param('caps')
724
        );
725
    }
726
727
728
    /**
729
     * Returns an array describing which fields can be protected, and which actually were removed this request
730
     *
731
     * @param $model
732
     * @param $results_so_far
733
     * @param $protected
734
     * @return array results
735
     * @throws EE_Error
736
     * @since 4.9.74.p
737
     */
738
    protected function addProtectedProperty(EEM_Base $model, $results_so_far, $protected)
739
    {
740
        if (! $protected || ! $model->hasPassword()) {
741
            return $results_so_far;
742
        }
743
        $password_field = $model->getPasswordField();
744
        $all_protected = array_merge(
745
            array($password_field->get_name()),
746
            $password_field->protectedFields()
747
        );
748
        $fields_included = array_keys($results_so_far);
749
        $fields_included = array_intersect(
750
            $all_protected,
751
            $fields_included
752
        );
753
        foreach ($fields_included as $field_name) {
754
            $results_so_far['_protected'][] = $field_name ;
755
        }
756
        return $results_so_far;
757
    }
758
759
760
    /**
761
     * Creates a REST entity array (JSON object we're going to return in the response, but
762
     * for now still a PHP array, but soon enough we'll call json_encode on it, don't worry),
763
     * from $wpdb->get_row( $sql, ARRAY_A)
764
     *
765
     * @param EEM_Base $model
766
     * @param array    $db_row
767
     * @return array entity mostly ready for converting to JSON and sending in the response
768
     * @throws EE_Error
769
     * @throws InvalidArgumentException
770
     * @throws InvalidDataTypeException
771
     * @throws InvalidInterfaceException
772
     * @throws ReflectionException
773
     * @throws RestException
774
     */
775
    protected function createBareEntityFromWpdbResults(EEM_Base $model, $db_row)
776
    {
777
        $result = $model->deduce_fields_n_values_from_cols_n_values($db_row);
778
        $result = array_intersect_key(
779
            $result,
780
            $this->getModelVersionInfo()->fieldsOnModelInThisVersion($model)
781
        );
782
        // if this is a CPT, we need to set the global $post to it,
783
        // otherwise shortcodes etc won't work properly while rendering it
784
        if ($model instanceof \EEM_CPT_Base) {
785
            $do_chevy_shuffle = true;
786
        } else {
787
            $do_chevy_shuffle = false;
788
        }
789
        if ($do_chevy_shuffle) {
790
            global $post;
791
            $old_post = $post;
792
            $post = get_post($result[ $model->primary_key_name() ]);
793
            if (! $post instanceof WP_Post) {
0 ignored issues
show
Bug introduced by
The class WP_Post does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
794
                // well that's weird, because $result is what we JUST fetched from the database
795
                throw new RestException(
796
                    'error_fetching_post_from_database_results',
797
                    esc_html__(
798
                        'An item was retrieved from the database but it\'s not a WP_Post like it should be.',
799
                        'event_espresso'
800
                    )
801
                );
802
            }
803
            $model_object_classname = 'EE_' . $model->get_this_model_name();
804
            $post->{$model_object_classname} = \EE_Registry::instance()->load_class(
805
                $model_object_classname,
806
                $result,
807
                false,
808
                false
809
            );
810
        }
811
        foreach ($result as $field_name => $field_value) {
812
            $field_obj = $model->field_settings_for($field_name);
813
            if ($this->isSubclassOfOne($field_obj, $this->getModelVersionInfo()->fieldsIgnored())) {
814
                unset($result[ $field_name ]);
815
            } elseif ($this->isSubclassOfOne(
816
                $field_obj,
817
                $this->getModelVersionInfo()->fieldsThatHaveRenderedFormat()
818
            )
819
            ) {
820
                $result[ $field_name ] = array(
821
                    'raw'      => $this->prepareFieldObjValueForJson($field_obj, $field_value),
822
                    'rendered' => $this->prepareFieldObjValueForJson($field_obj, $field_value, 'pretty'),
823
                );
824
            } elseif ($this->isSubclassOfOne(
825
                $field_obj,
826
                $this->getModelVersionInfo()->fieldsThatHavePrettyFormat()
827
            )
828
            ) {
829
                $result[ $field_name ] = array(
830
                    'raw'    => $this->prepareFieldObjValueForJson($field_obj, $field_value),
831
                    'pretty' => $this->prepareFieldObjValueForJson($field_obj, $field_value, 'pretty'),
832
                );
833
            } elseif ($field_obj instanceof \EE_Datetime_Field) {
834
                $field_value = $field_obj->prepare_for_set_from_db($field_value);
835
                // if the value is null, but we're not supposed to permit null, then set to the field's default
836
                if (is_null($field_value)) {
837
                    $field_value = $field_obj->getDefaultDateTimeObj();
838
                }
839
                if (is_null($field_value)) {
840
                    $gmt_date = $local_date = ModelDataTranslator::prepareFieldValuesForJson(
841
                        $field_obj,
842
                        $field_value,
843
                        $this->getModelVersionInfo()->requestedVersion()
844
                    );
845
                } else {
846
                    $timezone = $field_value->getTimezone();
847
                    EEH_DTT_Helper::setTimezone($field_value, new DateTimeZone('UTC'));
848
                    $gmt_date = ModelDataTranslator::prepareFieldValuesForJson(
849
                        $field_obj,
850
                        $field_value,
851
                        $this->getModelVersionInfo()->requestedVersion()
852
                    );
853
                    EEH_DTT_Helper::setTimezone($field_value, $timezone);
854
                    $local_date = ModelDataTranslator::prepareFieldValuesForJson(
855
                        $field_obj,
856
                        $field_value,
857
                        $this->getModelVersionInfo()->requestedVersion()
858
                    );
859
                }
860
                $result[ $field_name . '_gmt' ] = $gmt_date;
861
                $result[ $field_name ] = $local_date;
862
            } else {
863
                $result[ $field_name ] = $this->prepareFieldObjValueForJson($field_obj, $field_value);
864
            }
865
        }
866
        if ($do_chevy_shuffle) {
867
            $post = $old_post;
868
        }
869
        return $result;
870
    }
871
872
873
    /**
874
     * Takes a value all the way from the DB representation, to the model object's representation, to the
875
     * user-facing PHP representation, to the REST API representation. (Assumes you've already taken from the DB
876
     * representation using $field_obj->prepare_for_set_from_db())
877
     *
878
     * @param EE_Model_Field_Base $field_obj
879
     * @param mixed               $value  as it's stored on a model object
880
     * @param string              $format valid values are 'normal' (default), 'pretty', 'datetime_obj'
881
     * @return mixed
882
     * @throws RestException if $value contains a PHP object
883
     * @throws EE_Error
884
     */
885
    protected function prepareFieldObjValueForJson(EE_Model_Field_Base $field_obj, $value, $format = 'normal')
886
    {
887
        $value = $field_obj->prepare_for_set_from_db($value);
888
        switch ($format) {
889
            case 'pretty':
890
                $value = $field_obj->prepare_for_pretty_echoing($value);
891
                break;
892
            case 'normal':
893
            default:
894
                $value = $field_obj->prepare_for_get($value);
895
                break;
896
        }
897
        return ModelDataTranslator::prepareFieldValuesForJson(
898
            $field_obj,
899
            $value,
900
            $this->getModelVersionInfo()->requestedVersion()
901
        );
902
    }
903
904
905
    /**
906
     * Adds a few extra fields to the entity response
907
     *
908
     * @param EEM_Base $model
909
     * @param array    $db_row
910
     * @param array    $entity_array
911
     * @return array modified entity
912
     * @throws EE_Error
913
     */
914
    protected function addExtraFields(EEM_Base $model, $db_row, $entity_array)
915
    {
916
        if ($model instanceof EEM_CPT_Base) {
917
            $entity_array['link'] = get_permalink($db_row[ $model->get_primary_key_field()->get_qualified_column() ]);
918
        }
919
        return $entity_array;
920
    }
921
922
923
    /**
924
     * Gets links we want to add to the response
925
     *
926
     * @param EEM_Base         $model
927
     * @param array            $db_row
928
     * @param array            $entity_array
929
     * @return array the _links item in the entity
930
     * @throws EE_Error
931
     * @global WP_REST_Server $wp_rest_server
932
     */
933
    protected function getEntityLinks($model, $db_row, $entity_array)
934
    {
935
        // add basic links
936
        $links = array();
937
        if ($model->has_primary_key_field()) {
938
            $links['self'] = array(
939
                array(
940
                    'href' => $this->getVersionedLinkTo(
941
                        EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
942
                        . '/'
943
                        . $entity_array[ $model->primary_key_name() ]
944
                    ),
945
                ),
946
            );
947
        }
948
        $links['collection'] = array(
949
            array(
950
                'href' => $this->getVersionedLinkTo(
951
                    EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
952
                ),
953
            ),
954
        );
955
        // add links to related models
956
        if ($model->has_primary_key_field()) {
957
            foreach ($this->getModelVersionInfo()->relationSettings($model) as $relation_name => $relation_obj) {
958
                $related_model_part = Read::getRelatedEntityName($relation_name, $relation_obj);
959
                $links[ EED_Core_Rest_Api::ee_api_link_namespace . $related_model_part ] = array(
960
                    array(
961
                        'href'   => $this->getVersionedLinkTo(
962
                            EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
963
                            . '/'
964
                            . $entity_array[ $model->primary_key_name() ]
965
                            . '/'
966
                            . $related_model_part
967
                        ),
968
                        'single' => $relation_obj instanceof EE_Belongs_To_Relation,
969
                    ),
970
                );
971
            }
972
        }
973
        return $links;
974
    }
975
976
977
    /**
978
     * Adds the included models indicated in the request to the entity provided
979
     *
980
     * @param EEM_Base        $model
981
     * @param WP_REST_Request $rest_request
982
     * @param array           $entity_array
983
     * @param array           $db_row
984
     * @param boolean         $included_items_protected if the original item is password protected, don't include any related models.
985
     * @return array the modified entity
986
     * @throws DomainException
987
     * @throws EE_Error
988
     * @throws InvalidArgumentException
989
     * @throws InvalidDataTypeException
990
     * @throws InvalidInterfaceException
991
     * @throws ModelConfigurationException
992
     * @throws ReflectionException
993
     * @throws UnexpectedEntityException
994
     */
995
    protected function includeRequestedModels(
996
        EEM_Base $model,
997
        WP_REST_Request $rest_request,
998
        $entity_array,
999
        $db_row = array(),
1000
        $included_items_protected = false
1001
    ) {
1002
        // if $db_row not included, hope the entity array has what we need
1003
        if (! $db_row) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $db_row of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1004
            $db_row = $entity_array;
1005
        }
1006
        $relation_settings = $this->getModelVersionInfo()->relationSettings($model);
1007
        foreach ($relation_settings as $relation_name => $relation_obj) {
1008
            $related_fields_to_include = $this->explodeAndGetItemsPrefixedWith(
1009
                $rest_request->get_param('include'),
1010
                $relation_name
1011
            );
1012
            $related_fields_to_calculate = $this->explodeAndGetItemsPrefixedWith(
1013
                $rest_request->get_param('calculate'),
1014
                $relation_name
1015
            );
1016
            // did they specify they wanted to include a related model, or
1017
            // specific fields from a related model?
1018
            // or did they specify to calculate a field from a related model?
1019
            if ($related_fields_to_include || $related_fields_to_calculate) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $related_fields_to_include of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
Bug Best Practice introduced by
The expression $related_fields_to_calculate of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1020
                // if so, we should include at least some part of the related model
1021
                $pretend_related_request = new WP_REST_Request();
1022
                $pretend_related_request->set_query_params(
1023
                    array(
1024
                        'caps'      => $rest_request->get_param('caps'),
1025
                        'include'   => $related_fields_to_include,
1026
                        'calculate' => $related_fields_to_calculate,
1027
                        'password' => $rest_request->get_param('password')
1028
                    )
1029
                );
1030
                $pretend_related_request->add_header('no_rest_headers', true);
1031
                $primary_model_query_params = $model->alter_query_params_to_restrict_by_ID(
1032
                    $model->get_index_primary_key_string(
1033
                        $model->deduce_fields_n_values_from_cols_n_values($db_row)
1034
                    )
1035
                );
1036
                if (! $included_items_protected) {
1037
                    try {
1038
                        $related_results = $this->getEntitiesFromRelationUsingModelQueryParams(
1039
                            $primary_model_query_params,
1040
                            $relation_obj,
1041
                            $pretend_related_request
1042
                        );
1043
                    } catch (RestException $e) {
1044
                        $related_results = null;
1045
                    }
1046
                } else {
1047
                    // they're protected, hide them.
1048
                    $related_results = null;
1049
                    $entity_array['_protected'][] = Read::getRelatedEntityName($relation_name, $relation_obj);
1050
                }
1051
                if ($related_results instanceof WP_Error || $related_results === null) {
0 ignored issues
show
Bug introduced by
The class WP_Error does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
1052
                    $related_results = $relation_obj instanceof EE_Belongs_To_Relation ? null : array();
1053
                }
1054
                $entity_array[ Read::getRelatedEntityName($relation_name, $relation_obj) ] = $related_results;
1055
            }
1056
        }
1057
        return $entity_array;
1058
    }
1059
1060
    /**
1061
     * If the user has requested only specific properties (including meta properties like _links or _protected)
1062
     * remove everything else.
1063
     * @since 4.9.74.p
1064
     * @param EEM_Base $model
1065
     * @param WP_REST_Request $rest_request
1066
     * @param $entity_array
1067
     * @return array
1068
     * @throws EE_Error
1069
     */
1070
    protected function includeOnlyRequestedProperties(
1071
        EEM_Base $model,
1072
        WP_REST_Request $rest_request,
1073
        $entity_array
1074
    ) {
1075
1076
        $includes_for_this_model = $this->explodeAndGetItemsPrefixedWith($rest_request->get_param('include'), '');
1077
        $includes_for_this_model = $this->removeModelNamesFromArray($includes_for_this_model);
1078
        // if they passed in * or didn't specify any includes, return everything
1079
        if (! empty($includes_for_this_model) && ! in_array('*', $includes_for_this_model, true)) {
1080
            if ($model->has_primary_key_field()) {
1081
                // always include the primary key. ya just gotta know that at least
1082
                $includes_for_this_model[] = $model->primary_key_name();
1083
            }
1084
            if ($this->explodeAndGetItemsPrefixedWith($rest_request->get_param('calculate'), '')) {
1085
                $includes_for_this_model[] = '_calculated_fields';
1086
            }
1087
            $entity_array = array_intersect_key($entity_array, array_flip($includes_for_this_model));
1088
        }
1089
        return $entity_array;
1090
    }
1091
1092
1093
    /**
1094
     * Returns a new array with all the names of models removed. Eg
1095
     * array( 'Event', 'Datetime.*', 'foobar' ) would become array( 'Datetime.*', 'foobar' )
1096
     *
1097
     * @param array $arr
1098
     * @return array
1099
     */
1100
    private function removeModelNamesFromArray($arr)
1101
    {
1102
        return array_diff($arr, array_keys(EE_Registry::instance()->non_abstract_db_models));
1103
    }
1104
1105
1106
    /**
1107
     * Gets the calculated fields for the response
1108
     *
1109
     * @param EEM_Base        $model
1110
     * @param array           $wpdb_row
1111
     * @param WP_REST_Request $rest_request
1112
     * @param boolean         $row_is_protected whether this row is password protected or not
1113
     * @return stdClass the _calculations item in the entity
1114
     * @throws EE_Error
1115
     * @throws RestException if a default value has a PHP object, which should never do (and if we
1116
     *                                          did, let's know about it ASAP, so let the exception bubble up)
1117
     * @throws UnexpectedEntityException
1118
     */
1119
    protected function getEntityCalculations($model, $wpdb_row, $rest_request, $row_is_protected = false)
1120
    {
1121
        $calculated_fields = $this->explodeAndGetItemsPrefixedWith(
1122
            $rest_request->get_param('calculate'),
1123
            ''
1124
        );
1125
        // note: setting calculate=* doesn't do anything
1126
        $calculated_fields_to_return = new stdClass();
1127
        $protected_fields = array();
1128
        foreach ($calculated_fields as $field_to_calculate) {
1129
            try {
1130
                // it's password protected, so they shouldn't be able to read this. Remove the value
1131
                $schema = $this->fields_calculator->getJsonSchemaForModel($model);
1132
                if ($row_is_protected
1133
                    && isset($schema['properties'][ $field_to_calculate ]['protected'])
1134
                    && $schema['properties'][ $field_to_calculate ]['protected']) {
1135
                    $calculated_value = null;
1136
                    $protected_fields[] = $field_to_calculate;
1137
                    if ($schema['properties'][ $field_to_calculate ]['type']) {
1138
                        switch ($schema['properties'][ $field_to_calculate ]['type']) {
1139
                            case 'boolean':
1140
                                $calculated_value = false;
1141
                                break;
1142
                            case 'integer':
1143
                                $calculated_value = 0;
1144
                                break;
1145
                            case 'string':
1146
                                $calculated_value = '';
1147
                                break;
1148
                            case 'array':
1149
                                $calculated_value = array();
1150
                                break;
1151
                            case 'object':
1152
                                $calculated_value = new stdClass();
1153
                                break;
1154
                        }
1155
                    }
1156
                } else {
1157
                    $calculated_value = ModelDataTranslator::prepareFieldValueForJson(
1158
                        null,
1159
                        $this->fields_calculator->retrieveCalculatedFieldValue(
1160
                            $model,
1161
                            $field_to_calculate,
1162
                            $wpdb_row,
1163
                            $rest_request,
1164
                            $this
1165
                        ),
1166
                        $this->getModelVersionInfo()->requestedVersion()
1167
                    );
1168
                }
1169
                $calculated_fields_to_return->{$field_to_calculate} = $calculated_value;
1170
            } catch (RestException $e) {
1171
                // if we don't have permission to read it, just leave it out. but let devs know about the problem
1172
                $this->setResponseHeader(
1173
                    'Notices-Field-Calculation-Errors['
1174
                    . $e->getStringCode()
1175
                    . ']['
1176
                    . $model->get_this_model_name()
1177
                    . ']['
1178
                    . $field_to_calculate
1179
                    . ']',
1180
                    $e->getMessage()
1181
                );
1182
            }
1183
        }
1184
        $calculated_fields_to_return->_protected = $protected_fields;
1185
        return $calculated_fields_to_return;
1186
    }
1187
1188
1189
    /**
1190
     * Gets the full URL to the resource, taking the requested version into account
1191
     *
1192
     * @param string $link_part_after_version_and_slash eg "events/10/datetimes"
1193
     * @return string url eg "http://mysite.com/wp-json/ee/v4.6/events/10/datetimes"
1194
     * @throws EE_Error
1195
     */
1196
    public function getVersionedLinkTo($link_part_after_version_and_slash)
1197
    {
1198
        return rest_url(
1199
            EED_Core_Rest_Api::get_versioned_route_to(
1200
                $link_part_after_version_and_slash,
1201
                $this->getModelVersionInfo()->requestedVersion()
1202
            )
1203
        );
1204
    }
1205
1206
1207
    /**
1208
     * Gets the correct lowercase name for the relation in the API according
1209
     * to the relation's type
1210
     *
1211
     * @param string                  $relation_name
1212
     * @param EE_Model_Relation_Base $relation_obj
1213
     * @return string
1214
     */
1215
    public static function getRelatedEntityName($relation_name, $relation_obj)
1216
    {
1217
        if ($relation_obj instanceof EE_Belongs_To_Relation) {
1218
            return strtolower($relation_name);
1219
        }
1220
        return EEH_Inflector::pluralize_and_lower($relation_name);
1221
    }
1222
1223
1224
    /**
1225
     * Gets the one model object with the specified id for the specified model
1226
     *
1227
     * @param EEM_Base        $model
1228
     * @param WP_REST_Request $request
1229
     * @return array
1230
     * @throws EE_Error
1231
     * @throws InvalidArgumentException
1232
     * @throws InvalidDataTypeException
1233
     * @throws InvalidInterfaceException
1234
     * @throws ModelConfigurationException
1235
     * @throws ReflectionException
1236
     * @throws RestException
1237
     * @throws RestPasswordIncorrectException
1238
     * @throws RestPasswordRequiredException
1239
     * @throws UnexpectedEntityException
1240
     * @throws DomainException
1241
     */
1242
    public function getEntityFromModel($model, $request)
1243
    {
1244
        $context = $this->validateContext($request->get_param('caps'));
1245
        return $this->getOneOrReportPermissionError($model, $request, $context);
1246
    }
1247
1248
1249
    /**
1250
     * If a context is provided which isn't valid, maybe it was added in a future
1251
     * version so just treat it as a default read
1252
     *
1253
     * @param string $context
1254
     * @return string array key of EEM_Base::cap_contexts_to_cap_action_map()
1255
     */
1256
    public function validateContext($context)
1257
    {
1258
        if (! $context) {
1259
            $context = EEM_Base::caps_read;
1260
        }
1261
        $valid_contexts = EEM_Base::valid_cap_contexts();
1262
        if (in_array($context, $valid_contexts, true)) {
1263
            return $context;
1264
        }
1265
        return EEM_Base::caps_read;
1266
    }
1267
1268
1269
    /**
1270
     * Verifies the passed in value is an allowable default where conditions value.
1271
     *
1272
     * @param $default_query_params
1273
     * @return string
1274
     */
1275
    public function validateDefaultQueryParams($default_query_params)
1276
    {
1277
        $valid_default_where_conditions_for_api_calls = array(
1278
            EEM_Base::default_where_conditions_all,
1279
            EEM_Base::default_where_conditions_minimum_all,
1280
            EEM_Base::default_where_conditions_minimum_others,
1281
        );
1282
        if (! $default_query_params) {
1283
            $default_query_params = EEM_Base::default_where_conditions_all;
1284
        }
1285
        if (in_array(
1286
            $default_query_params,
1287
            $valid_default_where_conditions_for_api_calls,
1288
            true
1289
        )) {
1290
            return $default_query_params;
1291
        } else {
1292
            return EEM_Base::default_where_conditions_all;
1293
        }
1294
    }
1295
1296
1297
    /**
1298
     * 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.
1299
     * Note: right now the query parameter keys for fields (and related fields)
1300
     * can be left as-is, but it's quite possible this will change someday.
1301
     * Also, this method's contents might be candidate for moving to Model_Data_Translator
1302
     *
1303
     * @param EEM_Base $model
1304
     * @param          $query_params
1305
     * @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)
1306
     *                                    or FALSE to indicate that absolutely no results should be returned
1307
     * @throws EE_Error
1308
     * @throws InvalidArgumentException
1309
     * @throws InvalidDataTypeException
1310
     * @throws InvalidInterfaceException
1311
     * @throws RestException
1312
     * @throws DomainException
1313
     */
1314
    public function createModelQueryParams($model, $query_params)
1315
    {
1316
        $model_query_params = array();
1317 View Code Duplication
        if (isset($query_params['where'])) {
1318
            $model_query_params[0] = ModelDataTranslator::prepareConditionsQueryParamsForModels(
1319
                $query_params['where'],
1320
                $model,
1321
                $this->getModelVersionInfo()->requestedVersion()
1322
            );
1323
        }
1324
        if (isset($query_params['order_by'])) {
1325
            $order_by = $query_params['order_by'];
1326
        } elseif (isset($query_params['orderby'])) {
1327
            $order_by = $query_params['orderby'];
1328
        } else {
1329
            $order_by = null;
1330
        }
1331
        if ($order_by !== null) {
1332
            if (is_array($order_by)) {
1333
                $order_by = ModelDataTranslator::prepareFieldNamesInArrayKeysFromJson($order_by);
1334
            } else {
1335
                // it's a single item
1336
                $order_by = ModelDataTranslator::prepareFieldNameFromJson($order_by);
1337
            }
1338
            $model_query_params['order_by'] = $order_by;
1339
        }
1340
        if (isset($query_params['group_by'])) {
1341
            $group_by = $query_params['group_by'];
1342
        } elseif (isset($query_params['groupby'])) {
1343
            $group_by = $query_params['groupby'];
1344
        } else {
1345
            $group_by = array_keys($model->get_combined_primary_key_fields());
1346
        }
1347
        // make sure they're all real names
1348
        if (is_array($group_by)) {
1349
            $group_by = ModelDataTranslator::prepareFieldNamesFromJson($group_by);
1350
        }
1351
        if ($group_by !== null) {
1352
            $model_query_params['group_by'] = $group_by;
1353
        }
1354 View Code Duplication
        if (isset($query_params['having'])) {
1355
            $model_query_params['having'] = ModelDataTranslator::prepareConditionsQueryParamsForModels(
1356
                $query_params['having'],
1357
                $model,
1358
                $this->getModelVersionInfo()->requestedVersion()
1359
            );
1360
        }
1361
        if (isset($query_params['order'])) {
1362
            $model_query_params['order'] = $query_params['order'];
1363
        }
1364
        if (isset($query_params['mine'])) {
1365
            $model_query_params = $model->alter_query_params_to_only_include_mine($model_query_params);
1366
        }
1367
        if (isset($query_params['limit'])) {
1368
            // limit should be either a string like '23' or '23,43', or an array with two items in it
1369
            if (! is_array($query_params['limit'])) {
1370
                $limit_array = explode(',', (string) $query_params['limit']);
1371
            } else {
1372
                $limit_array = $query_params['limit'];
1373
            }
1374
            $sanitized_limit = array();
1375
            foreach ($limit_array as $key => $limit_part) {
1376
                if ($this->debug_mode && (! is_numeric($limit_part) || count($sanitized_limit) > 2)) {
1377
                    throw new EE_Error(
1378
                        sprintf(
1379
                            __(
1380
                            // @codingStandardsIgnoreStart
1381
                                '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.',
1382
                                // @codingStandardsIgnoreEnd
1383
                                'event_espresso'
1384
                            ),
1385
                            wp_json_encode($query_params['limit'])
1386
                        )
1387
                    );
1388
                }
1389
                $sanitized_limit[] = (int) $limit_part;
1390
            }
1391
            $model_query_params['limit'] = implode(',', $sanitized_limit);
1392
        } else {
1393
            $model_query_params['limit'] = EED_Core_Rest_Api::get_default_query_limit();
1394
        }
1395
        if (isset($query_params['caps'])) {
1396
            $model_query_params['caps'] = $this->validateContext($query_params['caps']);
1397
        } else {
1398
            $model_query_params['caps'] = EEM_Base::caps_read;
1399
        }
1400
        if (isset($query_params['default_where_conditions'])) {
1401
            $model_query_params['default_where_conditions'] = $this->validateDefaultQueryParams(
1402
                $query_params['default_where_conditions']
1403
            );
1404
        }
1405
        // if this is a model protected by a password on another model, exclude the password protected
1406
        // entities by default. But if they passed in a password, try to show them all. If the password is wrong,
1407
        // though, they'll get an error (see Read::createEntityFromWpdbResult() which calls Read::checkPassword)
1408
        if ($model_query_params['caps'] === EEM_Base::caps_read
1409
            && empty($query_params['password'])
1410
            && ! $model->hasPassword()
1411
            && $model->restrictedByRelatedModelPassword()
1412
        ) {
1413
            $model_query_params['exclude_protected'] = true;
1414
        }
1415
1416
        return apply_filters('FHEE__Read__create_model_query_params', $model_query_params, $query_params, $model);
1417
    }
1418
1419
1420
    /**
1421
     * Changes the REST-style query params for use in the models
1422
     *
1423
     * @deprecated
1424
     * @param EEM_Base $model
1425
     * @param array    $query_params sub-array from @see EEM_Base::get_all()
1426
     * @return array
1427
     */
1428 View Code Duplication
    public function prepareRestQueryParamsKeyForModels($model, $query_params)
1429
    {
1430
        $model_ready_query_params = array();
1431
        foreach ($query_params as $key => $value) {
1432
            if (is_array($value)) {
1433
                $model_ready_query_params[ $key ] = $this->prepareRestQueryParamsKeyForModels($model, $value);
0 ignored issues
show
Deprecated Code introduced by
The method EventEspresso\core\libra...eryParamsKeyForModels() has been deprecated.

This method has been deprecated.

Loading history...
1434
            } else {
1435
                $model_ready_query_params[ $key ] = $value;
1436
            }
1437
        }
1438
        return $model_ready_query_params;
1439
    }
1440
1441
1442
    /**
1443
     * @deprecated instead use ModelDataTranslator::prepareFieldValuesFromJson()
1444
     * @param $model
1445
     * @param $query_params
1446
     * @return array
1447
     */
1448 View Code Duplication
    public function prepareRestQueryParamsValuesForModels($model, $query_params)
1449
    {
1450
        $model_ready_query_params = array();
1451
        foreach ($query_params as $key => $value) {
1452
            if (is_array($value)) {
1453
                $model_ready_query_params[ $key ] = $this->prepareRestQueryParamsValuesForModels($model, $value);
0 ignored issues
show
Deprecated Code introduced by
The method EventEspresso\core\libra...ParamsValuesForModels() has been deprecated with message: instead use ModelDataTranslator::prepareFieldValuesFromJson()

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1454
            } else {
1455
                $model_ready_query_params[ $key ] = $value;
1456
            }
1457
        }
1458
        return $model_ready_query_params;
1459
    }
1460
1461
1462
    /**
1463
     * Explodes the string on commas, and only returns items with $prefix followed by a period.
1464
     * If no prefix is specified, returns items with no period.
1465
     *
1466
     * @param string|array $string_to_explode eg "jibba,jabba, blah, blah, blah" or array('jibba', 'jabba' )
1467
     * @param string       $prefix            "Event" or "foobar"
1468
     * @return array $string_to_exploded exploded on COMMAS, and if a prefix was specified
1469
     *                                        we only return strings starting with that and a period; if no prefix was
1470
     *                                        specified we return all items containing NO periods
1471
     */
1472
    public function explodeAndGetItemsPrefixedWith($string_to_explode, $prefix)
1473
    {
1474
        if (is_string($string_to_explode)) {
1475
            $exploded_contents = explode(',', $string_to_explode);
1476
        } elseif (is_array($string_to_explode)) {
1477
            $exploded_contents = $string_to_explode;
1478
        } else {
1479
            $exploded_contents = array();
1480
        }
1481
        // if the string was empty, we want an empty array
1482
        $exploded_contents = array_filter($exploded_contents);
1483
        $contents_with_prefix = array();
1484
        foreach ($exploded_contents as $item) {
1485
            $item = trim($item);
1486
            // if no prefix was provided, so we look for items with no "." in them
1487
            if (! $prefix) {
1488
                // does this item have a period?
1489
                if (strpos($item, '.') === false) {
1490
                    // if not, then its what we're looking for
1491
                    $contents_with_prefix[] = $item;
1492
                }
1493
            } elseif (strpos($item, $prefix . '.') === 0) {
1494
                // this item has the prefix and a period, grab it
1495
                $contents_with_prefix[] = substr(
1496
                    $item,
1497
                    strpos($item, $prefix . '.') + strlen($prefix . '.')
1498
                );
1499
            } elseif ($item === $prefix) {
1500
                // this item is JUST the prefix
1501
                // so let's grab everything after, which is a blank string
1502
                $contents_with_prefix[] = '';
1503
            }
1504
        }
1505
        return $contents_with_prefix;
1506
    }
1507
1508
1509
    /**
1510
     * @param string $include_string @see Read:handle_request_get_all
1511
     * @param string $model_name
1512
     * @return array of fields for this model. If $model_name is provided, then
1513
     *                               the fields for that model, with the model's name removed from each.
1514
     *                               If $include_string was blank or '*' returns an empty array
1515
     * @throws EE_Error
1516
     * @deprecated since 4.8.36.rc.001 You should instead use Read::explode_and_get_items_prefixed_with.
1517
     *             Deprecated because its return values were really quite confusing- sometimes it returned
1518
     *             an empty array (when the include string was blank or '*') or sometimes it returned
1519
     *             array('*') (when you provided a model and a model of that kind was found).
1520
     *             Parses the $include_string so we fetch all the field names relating to THIS model
1521
     *             (ie have NO period in them), or for the provided model (ie start with the model
1522
     *             name and then a period).
1523
     */
1524
    public function extractIncludesForThisModel($include_string, $model_name = null)
1525
    {
1526
        if (is_array($include_string)) {
1527
            $include_string = implode(',', $include_string);
1528
        }
1529
        if ($include_string === '*' || $include_string === '') {
1530
            return array();
1531
        }
1532
        $includes = explode(',', $include_string);
1533
        $extracted_fields_to_include = array();
1534
        if ($model_name) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $model_name of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1535
            foreach ($includes as $field_to_include) {
1536
                $field_to_include = trim($field_to_include);
1537
                if (strpos($field_to_include, $model_name . '.') === 0) {
1538
                    // found the model name at the exact start
1539
                    $field_sans_model_name = str_replace($model_name . '.', '', $field_to_include);
1540
                    $extracted_fields_to_include[] = $field_sans_model_name;
1541
                } elseif ($field_to_include === $model_name) {
1542
                    $extracted_fields_to_include[] = '*';
1543
                }
1544
            }
1545
        } else {
1546
            // look for ones with no period
1547
            foreach ($includes as $field_to_include) {
1548
                $field_to_include = trim($field_to_include);
1549
                if (strpos($field_to_include, '.') === false
1550
                    && ! $this->getModelVersionInfo()->isModelNameInThisVersion($field_to_include)
1551
                ) {
1552
                    $extracted_fields_to_include[] = $field_to_include;
1553
                }
1554
            }
1555
        }
1556
        return $extracted_fields_to_include;
1557
    }
1558
1559
1560
    /**
1561
     * Gets the single item using the model according to the request in the context given, otherwise
1562
     * returns that it's inaccessible to the current user
1563
     *
1564
     * @param EEM_Base        $model
1565
     * @param WP_REST_Request $request
1566
     * @param null            $context
1567
     * @return array
1568
     * @throws EE_Error
1569
     * @throws InvalidArgumentException
1570
     * @throws InvalidDataTypeException
1571
     * @throws InvalidInterfaceException
1572
     * @throws ModelConfigurationException
1573
     * @throws ReflectionException
1574
     * @throws RestException
1575
     * @throws RestPasswordIncorrectException
1576
     * @throws RestPasswordRequiredException
1577
     * @throws UnexpectedEntityException
1578
     * @throws DomainException
1579
     */
1580
    public function getOneOrReportPermissionError(EEM_Base $model, WP_REST_Request $request, $context = null)
1581
    {
1582
        $query_params = array(array($model->primary_key_name() => $request->get_param('id')), 'limit' => 1);
1583
        if ($model instanceof EEM_Soft_Delete_Base) {
1584
            $query_params = $model->alter_query_params_so_deleted_and_undeleted_items_included($query_params);
1585
        }
1586
        $restricted_query_params = $query_params;
1587
        $restricted_query_params['caps'] = $context;
1588
        $this->setDebugInfo('model query params', $restricted_query_params);
1589
        $model_rows = $model->get_all_wpdb_results($restricted_query_params);
1590
        if (! empty($model_rows)) {
1591
            return $this->createEntityFromWpdbResult(
1592
                $model,
1593
                reset($model_rows),
1594
                $request
1595
            );
1596
        }
1597
        // ok let's test to see if we WOULD have found it, had we not had restrictions from missing capabilities
1598
        $lowercase_model_name = strtolower($model->get_this_model_name());
1599
        if ($model->exists($query_params)) {
1600
            // you got shafted- it existed but we didn't want to tell you!
1601
            throw new RestException(
1602
                'rest_user_cannot_' . $context,
1603
                sprintf(
1604
                    __('Sorry, you cannot %1$s this %2$s. Missing permissions are: %3$s', 'event_espresso'),
1605
                    $context,
1606
                    $lowercase_model_name,
1607
                    Capabilities::getMissingPermissionsString(
1608
                        $model,
1609
                        $context
1610
                    )
1611
                ),
1612
                array('status' => 403)
1613
            );
1614
        }
1615
        // it's not you. It just doesn't exist
1616
        throw new RestException(
1617
            sprintf('rest_%s_invalid_id', $lowercase_model_name),
1618
            sprintf(__('Invalid %s ID.', 'event_espresso'), $lowercase_model_name),
1619
            array('status' => 404)
1620
        );
1621
    }
1622
1623
    /**
1624
     * Checks that if this content requires a password to be read, that it's been provided and is correct.
1625
     * @since 4.9.74.p
1626
     * @param EEM_Base $model
1627
     * @param $model_row
1628
     * @param array $query_params Adds 'default_where_conditions' => 'minimum' to ensure we don't confuse trashed with
1629
     *                      password protected.
1630
     * @param WP_REST_Request $request
1631
     * @throws EE_Error
1632
     * @throws InvalidArgumentException
1633
     * @throws InvalidDataTypeException
1634
     * @throws InvalidInterfaceException
1635
     * @throws RestPasswordRequiredException
1636
     * @throws RestPasswordIncorrectException
1637
     * @throws ModelConfigurationException
1638
     * @throws ReflectionException
1639
     */
1640
    protected function checkPassword(EEM_Base $model, $model_row, $query_params, WP_REST_Request $request)
1641
    {
1642
        $query_params['default_where_conditions'] = 'minimum';
1643
        // stuff is only "protected" for front-end requests. Elsewhere, you either get full permission to access the object
1644
        // or you don't.
1645
        $request_caps = $request->get_param('caps');
1646
        if (isset($request_caps) && $request_caps !== EEM_Base::caps_read) {
1647
            return;
1648
        }
1649
        // if this entity requires a password, they better give it and it better be right!
1650
        if ($model->hasPassword()
1651
            && $model_row[ $model->getPasswordField()->get_qualified_column() ] !== '') {
1652
            if (empty($request['password'])) {
1653
                throw new RestPasswordRequiredException();
1654
            }
1655
            if (!hash_equals(
1656
                $model_row[ $model->getPasswordField()->get_qualified_column() ],
1657
                $request['password']
1658
            )) {
1659
                throw new RestPasswordIncorrectException();
1660
            }
1661
        } // wait! maybe this content is password protected
1662
        elseif ($model->restrictedByRelatedModelPassword()
1663
            && $request->get_param('caps') === EEM_Base::caps_read) {
1664
            $password_supplied = $request->get_param('password');
1665
            if (empty($password_supplied)) {
1666
                $query_params['exclude_protected'] = true;
1667
                if (!$model->exists($query_params)) {
1668
                    throw new RestPasswordRequiredException();
1669
                }
1670
            } else {
1671
                $query_params[0][ $model->modelChainAndPassword() ] = $password_supplied;
1672
                if (!$model->exists($query_params)) {
1673
                    throw new RestPasswordIncorrectException();
1674
                }
1675
            }
1676
        }
1677
    }
1678
}
1679