Completed
Branch BUG-10738-inconsistency-in-ses... (cda363)
by
unknown
13:38 queued 12s
created

Read::createModelQueryParams()   F

Complexity

Conditions 21
Paths > 20000

Size

Total Lines 93
Code Lines 64

Duplication

Lines 14
Ratio 15.05 %

Importance

Changes 0
Metric Value
cc 21
eloc 64
nc 24192
nop 2
dl 14
loc 93
rs 2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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