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

throwExceptionIfContainsSerializedData()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 12
nc 4
nop 1
dl 0
loc 21
rs 8.7624
c 0
b 0
f 0
1
<?php
2
namespace EventEspresso\core\libraries\rest_api;
3
4
use EE_Capabilities;
5
use EE_Datetime_Field;
6
use EE_Error;
7
use EE_Infinite_Integer_Field;
8
use EE_Maybe_Serialized_Simple_HTML_Field;
9
use EE_Model_Field_Base;
10
use EE_Serialized_Text_Field;
11
use EEM_Base;
12
13
if (! defined('EVENT_ESPRESSO_VERSION')) {
14
    exit('No direct script access allowed');
15
}
16
17
18
19
/**
20
 * Class Model_Data_Translator
21
 * Class for translating data between the EE4 models and JSON.
22
 * Some of this class needs to interpret an incoming array of query params from
23
 * the REST API and prepare it for use by the models. Some of this code seems duplicated
24
 * from the models but it's anticipated to diverge (because providing parameters
25
 * in the REST API is sometimes more difficult than in PHP directly. Eg, providing an array like
26
 * array( 'where' => array( 'EVT_ID' => array( '<', 100 ) ) ) in PHP is easy, but in a query string it needs to look
27
 * like
28
 * "where[EVT_ID][]=<&where[EVT_ID][]=100" is less intuitive, so we may want
29
 * to allow REST API query parameters to diverge from the format accepted by models)
30
 *
31
 * @package               Event Espresso
32
 * @subpackage
33
 * @author                Mike Nelson
34
 * @since                 4.8.36
35
 */
36
class ModelDataTranslator
37
{
38
39
    /**
40
     * We used to use -1 for infinity in the rest api, but that's ambiguous for
41
     * fields that COULD contain -1; so we use null
42
     */
43
    const EE_INF_IN_REST = null;
44
45
46
47
    /**
48
     * Prepares a possible array of input values from JSON for use by the models
49
     *
50
     * @param EE_Model_Field_Base $field_obj
51
     * @param mixed                $original_value_maybe_array
52
     * @param string               $requested_version
53
     * @param string               $timezone_string treat values as being in this timezone
54
     * @return mixed
55
     * @throws RestException
56
     */
57
    public static function prepareFieldValuesFromJson(
58
        $field_obj,
59
        $original_value_maybe_array,
60
        $requested_version,
61
        $timezone_string = 'UTC'
62
    ) {
63
        if (is_array($original_value_maybe_array)
64
            && ! $field_obj instanceof EE_Serialized_Text_Field
65
        ) {
66
            $new_value_maybe_array = array();
67
            foreach ($original_value_maybe_array as $array_key => $array_item) {
68
                $new_value_maybe_array[$array_key] = ModelDataTranslator::prepareFieldValueFromJson(
69
                    $field_obj,
70
                    $array_item,
71
                    $requested_version,
72
                    $timezone_string
73
                );
74
            }
75
        } else {
76
            $new_value_maybe_array = ModelDataTranslator::prepareFieldValueFromJson(
77
                $field_obj,
78
                $original_value_maybe_array,
79
                $requested_version,
80
                $timezone_string
81
            );
82
        }
83
        return $new_value_maybe_array;
84
    }
85
86
87
88
    /**
89
     * Prepares an array of field values FOR use in JSON/REST API
90
     *
91
     * @param EE_Model_Field_Base $field_obj
92
     * @param mixed                $original_value_maybe_array
93
     * @param string               $request_version (eg 4.8.36)
94
     * @return array
95
     */
96
    public static function prepareFieldValuesForJson($field_obj, $original_value_maybe_array, $request_version)
97
    {
98
        if (is_array($original_value_maybe_array)) {
99
            $new_value = array();
100
            foreach ($original_value_maybe_array as $key => $value) {
101
                $new_value[$key] = ModelDataTranslator::prepareFieldValuesForJson($field_obj, $value, $request_version);
102
            }
103
        } else {
104
            $new_value = ModelDataTranslator::prepareFieldValueForJson(
105
                $field_obj,
106
                $original_value_maybe_array,
107
                $request_version
108
            );
109
        }
110
        return $new_value;
111
    }
112
113
114
115
    /**
116
     * Prepares incoming data from the json or $_REQUEST parameters for the models'
117
     * "$query_params".
118
     *
119
     * @param EE_Model_Field_Base $field_obj
120
     * @param mixed                $original_value
121
     * @param string               $requested_version
122
     * @param string               $timezone_string treat values as being in this timezone
123
     * @return mixed
124
     * @throws RestException
125
     */
126
    public static function prepareFieldValueFromJson(
127
        $field_obj,
128
        $original_value,
129
        $requested_version,
130
        $timezone_string = 'UTC' // UTC
131
    ) {
132
        //check if they accidentally submitted an error value. If so throw an exception
133
        if (is_array($original_value)
134
            && isset($original_value['error_code'], $original_value['error_message'])) {
135
            throw new RestException(
136
                'rest_submitted_error_value',
137
                sprintf(
138
                    esc_html__(
139
                        'You tried to submit a JSON error object as a value for %1$s. That\'s not allowed.',
140
                        'event_espresso'
141
                    ),
142
                    $field_obj->get_name()
143
                ),
144
                array(
145
                    'status' => 400,
146
                )
147
            );
148
        }
149
        //double-check for serialized PHP. We never accept serialized PHP. No way Jose.
150
        ModelDataTranslator::throwExceptionIfContainsSerializedData($original_value);
151
        $timezone_string = $timezone_string !== '' ? $timezone_string : get_option('timezone_string', '');
152
        $new_value = null;
0 ignored issues
show
Unused Code introduced by
$new_value is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
153
        //walk through the submitted data and double-check for serialized PHP. We never accept serialized PHP. No
154
        // way Jose.
155
        ModelDataTranslator::throwExceptionIfContainsSerializedData($original_value);
156
        if ($field_obj instanceof EE_Infinite_Integer_Field
157
            && in_array($original_value, array(null, ''), true)
158
        ) {
159
            $new_value = EE_INF;
160
        } elseif ($field_obj instanceof EE_Datetime_Field) {
161
            list($offset_sign, $offset_secs) = ModelDataTranslator::parseTimezoneOffset(
162
                $field_obj->get_timezone_offset(
163
                    new \DateTimeZone($timezone_string),
164
                    $original_value
165
                )
166
            );
167
            $offset_string =
168
                str_pad(
169
                    floor($offset_secs / HOUR_IN_SECONDS),
170
                    2,
171
                    '0',
172
                    STR_PAD_LEFT
173
                )
174
                . ':'
175
                . str_pad(
176
                    ($offset_secs % HOUR_IN_SECONDS) / MINUTE_IN_SECONDS,
177
                    2,
178
                    '0',
179
                    STR_PAD_LEFT
180
                );
181
            $new_value = rest_parse_date($original_value . $offset_sign . $offset_string);
182
        } else {
183
            $new_value = $original_value;
184
        }
185
        return $new_value;
186
    }
187
188
189
190
    /**
191
     * Throws an exception if $data is a serialized PHP string (or somehow an actually PHP object, although I don't
192
     * think that can happen). If $data is an array, recurses into its keys and values
193
     * @param mixed $data
194
     * @throws RestException
195
     * @return void
196
     */
197
    public static function throwExceptionIfContainsSerializedData($data)
198
    {
199
        if (is_array($data)) {
200
            foreach ($data as $key => $value) {
201
                ModelDataTranslator::throwExceptionIfContainsSerializedData($key);
202
                ModelDataTranslator::throwExceptionIfContainsSerializedData($value);
203
            }
204
        } else {
205
            if (is_serialized($data) || is_object($data)) {
206
                throw new RestException(
207
                    'serialized_data_submission_prohibited',
208
                    esc_html__(
209
                        // @codingStandardsIgnoreStart
210
                        'You tried to submit a string of serialized text. Serialized PHP is prohibited over the EE4 REST API.',
211
                        // @codingStandardsIgnoreEnd
212
                        'event_espresso'
213
                    )
214
                );
215
            }
216
        }
217
    }
218
219
220
221
    /**
222
     * determines what's going on with them timezone strings
223
     *
224
     * @param int $timezone_offset
225
     * @return array
226
     */
227
    private static function parseTimezoneOffset($timezone_offset)
228
    {
229
        $first_char = substr((string)$timezone_offset, 0, 1);
230
        if ($first_char === '+' || $first_char === '-') {
231
            $offset_sign = $first_char;
232
            $offset_secs = substr((string)$timezone_offset, 1);
233
        } else {
234
            $offset_sign = '+';
235
            $offset_secs = $timezone_offset;
236
        }
237
        return array($offset_sign, $offset_secs);
238
    }
239
240
241
242
    /**
243
     * Prepares a field's value for display in the API
244
     *
245
     * @param EE_Model_Field_Base $field_obj
246
     * @param mixed                $original_value
247
     * @param string               $requested_version
248
     * @return mixed
249
     */
250
    public static function prepareFieldValueForJson($field_obj, $original_value, $requested_version)
251
    {
252
        if ($original_value === EE_INF) {
253
            $new_value = ModelDataTranslator::EE_INF_IN_REST;
254
        } elseif ($field_obj instanceof EE_Datetime_Field) {
255
            if (is_string($original_value)) {
256
                //did they submit a string of a unix timestamp?
257
                if (is_numeric($original_value)) {
258
                    $datetime_obj = new \DateTime();
259
                    $datetime_obj->setTimestamp((int)$original_value);
260
                } else {
261
                    //first, check if its a MySQL timestamp in GMT
262
                    $datetime_obj = \DateTime::createFromFormat('Y-m-d H:i:s', $original_value);
263
                }
264
                if (! $datetime_obj instanceof \DateTime) {
265
                    //so it's not a unix timestamp or a MySQL timestamp. Maybe its in the field's date/time format?
266
                    $datetime_obj = $field_obj->prepare_for_set($original_value);
267
                }
268
                $original_value = $datetime_obj;
269
            }
270
            if ($original_value instanceof \DateTime) {
271
                $new_value = $original_value->format('Y-m-d H:i:s');
272
            } elseif (is_int($original_value) || is_float($original_value)) {
273
                $new_value = date('Y-m-d H:i:s', $original_value);
274
            } elseif($original_value === null || $original_value === '') {
275
                $new_value = null;
276
            } else {
277
                //so it's not a datetime object, unix timestamp (as string or int),
278
                //MySQL timestamp, or even a string in the field object's format. So no idea what it is
279
                throw new \EE_Error(
280
                    sprintf(
281
                        esc_html__(
282
                        // @codingStandardsIgnoreStart
283
                            'The value "%1$s" for the field "%2$s" on model "%3$s" could not be understood. It should be a PHP DateTime, unix timestamp, MySQL date, or string in the format "%4$s".',
284
                            // @codingStandardsIgnoreEnd
285
                            'event_espressso'
286
                        ),
287
                        $original_value,
288
                        $field_obj->get_name(),
289
                        $field_obj->get_model_name(),
290
                        $field_obj->get_time_format() . ' ' . $field_obj->get_time_format()
291
                    )
292
                );
293
            }
294
            $new_value = mysql_to_rfc3339($new_value);
295
        } else {
296
            $new_value = $original_value;
297
        }
298
        //are we about to send an object? just don't. We have no good way to represent it in JSON.
299
        // can't just check using is_object() because that missed PHP incomplete objects
300
        if (! ModelDataTranslator::isRepresentableInJson($new_value)) {
301
            $new_value = array(
302
                'error_code' => 'php_object_not_return',
303
                'error_message' => esc_html__('The value of this field in the database is a PHP object, which can\'t be represented in JSON.', 'event_espresso')
304
            );
305
        }
306
        return apply_filters(
307
            'FHEE__EventEspresso\core\libraries\rest_api\Model_Data_Translator__prepare_field_for_rest_api',
308
            $new_value,
309
            $field_obj,
310
            $original_value,
311
            $requested_version
312
        );
313
    }
314
315
316
317
    /**
318
     * Prepares condition-query-parameters (like what's in where and having) from
319
     * the format expected in the API to use in the models
320
     *
321
     * @param array     $inputted_query_params_of_this_type
322
     * @param EEM_Base $model
323
     * @param string    $requested_version
324
     * @param boolean $writing whether this data will be written to the DB, or if we're just building a query.
325
     *                         If we're writing to the DB, we don't expect any operators, or any logic query parameters,
326
     *                         and we also won't accept serialized data unless the current user has unfiltered_html.
327
     * @return array
328
     * @throws \DomainException
329
     * @throws RestException
330
     * @throws EE_Error
331
     */
332
    public static function prepareConditionsQueryParamsForModels(
333
        $inputted_query_params_of_this_type,
334
        EEM_Base $model,
335
        $requested_version,
336
        $writing = false
337
    ) {
338
        $query_param_for_models = array();
339
        $valid_operators = $model->valid_operators();
340
        foreach ($inputted_query_params_of_this_type as $query_param_key => $query_param_value) {
341
            $is_gmt_datetime_field = false;
342
            $query_param_sans_stars = ModelDataTranslator::removeStarsAndAnythingAfterFromConditionQueryParamKey(
343
                $query_param_key
344
            );
345
            $field = ModelDataTranslator::deduceFieldFromQueryParam(
346
                $query_param_sans_stars,
347
                $model
348
            );
349
            //double-check is it a *_gmt field?
350
            if (! $field instanceof EE_Model_Field_Base
351
                && ModelDataTranslator::isGmtDateFieldName($query_param_sans_stars)
352
            ) {
353
                //yep, take off '_gmt', and find the field
354
                $query_param_key = ModelDataTranslator::removeGmtFromFieldName($query_param_sans_stars);
355
                $field = ModelDataTranslator::deduceFieldFromQueryParam(
356
                    $query_param_key,
357
                    $model
358
                );
359
                $timezone = 'UTC';
360
                $is_gmt_datetime_field = true;
361
            } elseif ($field instanceof EE_Datetime_Field) {
362
                //so it's not a GMT field. Set the timezone on the model to the default
363
                $timezone = \EEH_DTT_Helper::get_valid_timezone_string();
364
            } else {
365
                //just keep using what's already set for the timezone
366
                $timezone = $model->get_timezone();
367
            }
368
            if ($field instanceof EE_Model_Field_Base) {
369
                if (! $writing && is_array($query_param_value)) {
370 View Code Duplication
                    if (! \EEH_Array::is_array_numerically_and_sequentially_indexed($query_param_value)) {
371
                        if (defined('EE_REST_API_DEBUG_MODE') && EE_REST_API_DEBUG_MODE) {
372
                            throw new RestException(
373
                                'numerically_indexed_array_of_values_only',
374
                                sprintf(
375
                                    esc_html__(
376
                                        'The array provided for the parameter "%1$s" should be numerically indexed.',
377
                                        'event_espresso'
378
                                    ),
379
                                    $query_param_key
380
                                ),
381
                                array(
382
                                    'status' => 400,
383
                                )
384
                            );
385
                        }
386
                    }
387
                    //did they specify an operator?
388
                    if (isset($query_param_value[0])
389
                        && isset($valid_operators[$query_param_value[0]])
390
                    ) {
391
                        $op = $query_param_value[0];
392
                        $translated_value = array($op);
393
                        if (array_key_exists($op, $model->valid_in_style_operators())
394
                            && isset($query_param_value[1])
395
                            && ! isset($query_param_value[2])
396
                        ) {
397
                            $translated_value[] = ModelDataTranslator::prepareFieldValuesFromJson(
398
                                $field,
399
                                $query_param_value[1],
400
                                $requested_version,
401
                                $timezone
402
                            );
403
                        } elseif (array_key_exists($op, $model->valid_between_style_operators())
404
                            && isset($query_param_value[1], $query_param_value[2])
405
                            && !isset($query_param_value[3])
406
                        ) {
407
                            $translated_value[] = ModelDataTranslator::prepareFieldValuesFromJson(
408
                                $field,
409
                                $query_param_value[1],
410
                                $requested_version,
411
                                $timezone
412
                            );
413
                            $translated_value[] = ModelDataTranslator::prepareFieldValuesFromJson(
414
                                $field,
415
                                $query_param_value[2],
416
                                $requested_version,
417
                                $timezone
418
                            );
419
                        } elseif (array_key_exists($op, $model->valid_like_style_operators())
420
                            && isset($query_param_value[1])
421
                            && ! isset($query_param_value[2])
422
                        ) {
423
                            //we want to leave this value mostly-as-is (eg don't force it to be a float
424
                            //or a boolean or an enum value. Leave it as-is with wildcards etc)
425
                            //but do verify it at least doesn't have any serialized data
426
                            ModelDataTranslator::throwExceptionIfContainsSerializedData($query_param_value[1]);
427
                            $translated_value[] = $query_param_value[1];
428
                        } elseif (array_key_exists($op, $model->valid_null_style_operators())
0 ignored issues
show
Unused Code introduced by
This elseif statement is empty, and could be removed.

This check looks for the bodies of elseif statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These elseif bodies can be removed. If you have an empty elseif but statements in the else branch, consider inverting the condition.

Loading history...
429
                            && !isset($query_param_value[1])) {
430
                            //no arguments should have been provided, so don't look for any
431
                        } elseif (isset($query_param_value[1])
432
                            && !isset($query_param_value[2])
433
                            && ! array_key_exists(
434
                                $op,
435
                                array_merge(
436
                                    $model->valid_in_style_operators(),
437
                                    $model->valid_null_style_operators(),
438
                                    $model->valid_like_style_operators(),
439
                                    $model->valid_between_style_operators()
440
                                )
441
                            )
442
                        ) {
443
                            //it's a valid operator, but none of the exceptions. Treat it normally.
444
                            $translated_value[] = ModelDataTranslator::prepareFieldValuesFromJson(
445
                                $field,
446
                                $query_param_value[1],
447
                                $requested_version,
448
                                $timezone
449
                            );
450 View Code Duplication
                        } else {
451
                            //so they provided a valid operator, but wrong number of arguments
452
                            if (defined('EE_REST_API_DEBUG_MODE') && EE_REST_API_DEBUG_MODE) {
453
                                throw new RestException(
454
                                    'wrong_number_of_arguments',
455
                                    sprintf(
456
                                        esc_html__(
457
                                            'The operator you provided, "%1$s" had the wrong number of arguments',
458
                                            'event_espresso'
459
                                        ),
460
                                        $op
461
                                    ),
462
                                    array(
463
                                        'status' => 400,
464
                                    )
465
                                );
466
                            }
467
                            $translated_value = null;
468
                        }
469 View Code Duplication
                    } else {
470
                        //so they didn't provide a valid operator
471
                        if (defined('EE_REST_API_DEBUG_MODE') && EE_REST_API_DEBUG_MODE) {
472
                            throw new RestException(
473
                                'invalid_operator',
474
                                sprintf(
475
                                    esc_html__(
476
                                        'You provided an invalid parameter, with key "%1$s" and value "%2$s"',
477
                                        'event_espresso'
478
                                    ),
479
                                    $query_param_key,
480
                                    $query_param_value
481
                                ),
482
                                array(
483
                                    'status' => 400,
484
                                )
485
                            );
486
                        }
487
                        //if we aren't in debug mode, then just try our best to fulfill the user's request
488
                        $translated_value = null;
489
                    }
490
                } else {
491
                    $translated_value = ModelDataTranslator::prepareFieldValueFromJson(
492
                        $field,
493
                        $query_param_value,
494
                        $requested_version,
495
                        $timezone
496
                    );
497
                }
498
                if (
499
                    (isset($query_param_for_models[$query_param_key]) && $is_gmt_datetime_field)
500
                    ||
501
                    $translated_value === null
502
                ) {
503
                    //they have already provided a non-gmt field, ignore the gmt one. That's what WP core
504
                    //currently does (they might change it though). See https://core.trac.wordpress.org/ticket/39954
505
                    //OR we couldn't create a translated value from their input
506
                    continue;
507
                }
508
                $query_param_for_models[$query_param_key] = $translated_value;
509
            } else {
510
                //so this param doesn't correspond to a field eh?
511
                if ($writing) {
512
                    //always tell API clients about invalid parameters when they're creating data. Otherwise,
513
                    //they are probably going to create invalid data
514
                    throw new RestException(
515
                        'invalid_field',
516
                        sprintf(
517
                            esc_html__('You have provided an invalid parameter: "%1$s"', 'event_espresso'),
518
                            $query_param_key
519
                        )
520
                    );
521
                } else {
522
                    //so it's not for a field, is it a logic query param key?
523
                    if (in_array(
524
                        $query_param_sans_stars,
525
                        $model->logic_query_param_keys()
526
                    )) {
527
                        $query_param_for_models[$query_param_key] = ModelDataTranslator::prepareConditionsQueryParamsForModels(
528
                            $query_param_value,
529
                            $model,
530
                            $requested_version
531
                        );
532 View Code Duplication
                    } elseif (defined('EE_REST_API_DEBUG_MODE') && EE_REST_API_DEBUG_MODE) {
533
                        //only tell API clients they got it wrong if we're in debug mode
534
                        //otherwise try our best ot fulfill their request by ignoring this invalid data
535
                        throw new RestException(
536
                            'invalid_parameter',
537
                            sprintf(
538
                                esc_html__(
539
                                    'You provided an invalid parameter, with key "%1$s"',
540
                                    'event_espresso'
541
                                ),
542
                                $query_param_sans_stars
543
                            ),
544
                            array(
545
                                'status' => 400,
546
                            )
547
                        );
548
                    }
549
                }
550
            }
551
        }
552
        return $query_param_for_models;
553
    }
554
555
556
557
    /**
558
     * Mostly checks if the last 4 characters are "_gmt", indicating its a
559
     * gmt date field name
560
     *
561
     * @param string $field_name
562
     * @return boolean
563
     */
564
    public static function isGmtDateFieldName($field_name)
565
    {
566
        return substr(
567
            ModelDataTranslator::removeStarsAndAnythingAfterFromConditionQueryParamKey($field_name),
568
            -4,
569
            4
570
        ) === '_gmt';
571
    }
572
573
574
575
    /**
576
     * Removes the last "_gmt" part of a field name (and if there is no "_gmt" at the end, leave it alone)
577
     *
578
     * @param string $field_name
579
     * @return string
580
     */
581
    public static function removeGmtFromFieldName($field_name)
582
    {
583
        if (! ModelDataTranslator::isGmtDateFieldName($field_name)) {
584
            return $field_name;
585
        }
586
        $query_param_sans_stars = ModelDataTranslator::removeStarsAndAnythingAfterFromConditionQueryParamKey(
587
            $field_name
588
        );
589
        $query_param_sans_gmt_and_sans_stars = substr(
590
            $query_param_sans_stars,
591
            0,
592
            strrpos(
593
                $field_name,
594
                '_gmt'
595
            )
596
        );
597
        return str_replace($query_param_sans_stars, $query_param_sans_gmt_and_sans_stars, $field_name);
598
    }
599
600
601
602
    /**
603
     * Takes a field name from the REST API and prepares it for the model querying
604
     *
605
     * @param string $field_name
606
     * @return string
607
     */
608
    public static function prepareFieldNameFromJson($field_name)
609
    {
610
        if (ModelDataTranslator::isGmtDateFieldName($field_name)) {
611
            return ModelDataTranslator::removeGmtFromFieldName($field_name);
612
        }
613
        return $field_name;
614
    }
615
616
617
618
    /**
619
     * Takes array of field names from REST API and prepares for models
620
     *
621
     * @param array $field_names
622
     * @return array of field names (possibly include model prefixes)
623
     */
624
    public static function prepareFieldNamesFromJson(array $field_names)
625
    {
626
        $new_array = array();
627
        foreach ($field_names as $key => $field_name) {
628
            $new_array[$key] = ModelDataTranslator::prepareFieldNameFromJson($field_name);
629
        }
630
        return $new_array;
631
    }
632
633
634
635
    /**
636
     * Takes array where array keys are field names (possibly with model path prefixes)
637
     * from the REST API and prepares them for model querying
638
     *
639
     * @param array $field_names_as_keys
640
     * @return array
641
     */
642
    public static function prepareFieldNamesInArrayKeysFromJson(array $field_names_as_keys)
643
    {
644
        $new_array = array();
645
        foreach ($field_names_as_keys as $field_name => $value) {
646
            $new_array[ModelDataTranslator::prepareFieldNameFromJson($field_name)] = $value;
647
        }
648
        return $new_array;
649
    }
650
651
652
653
    /**
654
     * Prepares an array of model query params for use in the REST API
655
     *
656
     * @param array     $model_query_params
657
     * @param EEM_Base $model
658
     * @param string    $requested_version eg "4.8.36". If null is provided, defaults to the latest release of the EE4
659
     *                                     REST API
660
     * @return array which can be passed into the EE4 REST API when querying a model resource
661
     * @throws EE_Error
662
     */
663
    public static function prepareQueryParamsForRestApi(
664
        array $model_query_params,
665
        EEM_Base $model,
666
        $requested_version = null
667
    ) {
668
        if ($requested_version === null) {
669
            $requested_version = \EED_Core_Rest_Api::latest_rest_api_version();
670
        }
671
        $rest_query_params = $model_query_params;
672 View Code Duplication
        if (isset($model_query_params[0])) {
673
            $rest_query_params['where'] = ModelDataTranslator::prepareConditionsQueryParamsForRestApi(
674
                $model_query_params[0],
675
                $model,
676
                $requested_version
677
            );
678
            unset($rest_query_params[0]);
679
        }
680 View Code Duplication
        if (isset($model_query_params['having'])) {
681
            $rest_query_params['having'] = ModelDataTranslator::prepareConditionsQueryParamsForRestApi(
682
                $model_query_params['having'],
683
                $model,
684
                $requested_version
685
            );
686
        }
687
        return apply_filters(
688
            'FHEE__EventEspresso\core\libraries\rest_api\Model_Data_Translator__prepare_query_params_for_rest_api',
689
            $rest_query_params,
690
            $model_query_params,
691
            $model,
692
            $requested_version
693
        );
694
    }
695
696
697
698
    /**
699
     * Prepares all the sub-conditions query parameters (eg having or where conditions) for use in the rest api
700
     *
701
     * @param array     $inputted_query_params_of_this_type eg like the "where" or "having" conditions query params
702
     *                                                      passed into EEM_Base::get_all()
703
     * @param EEM_Base $model
704
     * @param string    $requested_version                  eg "4.8.36"
705
     * @return array ready for use in the rest api query params
706
     * @throws EE_Error
707
     * @throws ObjectDetectedException if somehow a PHP object were in the query params' values,
708
     *                                     (which would be really unusual)
709
     */
710
    public static function prepareConditionsQueryParamsForRestApi(
711
        $inputted_query_params_of_this_type,
712
        EEM_Base $model,
713
        $requested_version
714
    ) {
715
        $query_param_for_models = array();
716
        foreach ($inputted_query_params_of_this_type as $query_param_key => $query_param_value) {
717
            $field = ModelDataTranslator::deduceFieldFromQueryParam(
718
                ModelDataTranslator::removeStarsAndAnythingAfterFromConditionQueryParamKey($query_param_key),
719
                $model
720
            );
721
            if ($field instanceof EE_Model_Field_Base) {
722
                //did they specify an operator?
723
                if (is_array($query_param_value)) {
724
                    $op = $query_param_value[0];
725
                    $translated_value = array($op);
726
                    if (isset($query_param_value[1])) {
727
                        $value = $query_param_value[1];
728
                        $translated_value[1] = ModelDataTranslator::prepareFieldValuesForJson(
729
                            $field,
730
                            $value,
731
                            $requested_version
732
                        );
733
                    }
734
                } else {
735
                    $translated_value = ModelDataTranslator::prepareFieldValueForJson(
736
                        $field,
737
                        $query_param_value,
738
                        $requested_version
739
                    );
740
                }
741
                $query_param_for_models[$query_param_key] = $translated_value;
742
            } else {
743
                //so it's not for a field, assume it's a logic query param key
744
                $query_param_for_models[$query_param_key] = ModelDataTranslator::prepareConditionsQueryParamsForRestApi(
745
                    $query_param_value,
746
                    $model,
747
                    $requested_version
748
                );
749
            }
750
        }
751
        return $query_param_for_models;
752
    }
753
754
755
756
    /**
757
     * @param $condition_query_param_key
758
     * @return string
759
     */
760
    public static function removeStarsAndAnythingAfterFromConditionQueryParamKey($condition_query_param_key)
761
    {
762
        $pos_of_star = strpos($condition_query_param_key, '*');
763
        if ($pos_of_star === false) {
764
            return $condition_query_param_key;
765
        } else {
766
            $condition_query_param_sans_star = substr($condition_query_param_key, 0, $pos_of_star);
767
            return $condition_query_param_sans_star;
768
        }
769
    }
770
771
772
773
    /**
774
     * Takes the input parameter and finds the model field that it indicates.
775
     *
776
     * @param string    $query_param_name like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
777
     * @param EEM_Base $model
778
     * @return EE_Model_Field_Base
779
     * @throws EE_Error
780
     */
781
    public static function deduceFieldFromQueryParam($query_param_name, EEM_Base $model)
782
    {
783
        //ok, now proceed with deducing which part is the model's name, and which is the field's name
784
        //which will help us find the database table and column
785
        $query_param_parts = explode('.', $query_param_name);
786
        if (empty($query_param_parts)) {
787
            throw new EE_Error(
788
                sprintf(
789
                    __(
790
                        '_extract_column_name is empty when trying to extract column and table name from %s',
791
                        'event_espresso'
792
                    ),
793
                    $query_param_name
794
                )
795
            );
796
        }
797
        $number_of_parts = count($query_param_parts);
798
        $last_query_param_part = $query_param_parts[count($query_param_parts) - 1];
799
        if ($number_of_parts === 1) {
800
            $field_name = $last_query_param_part;
801
        } else {// $number_of_parts >= 2
802
            //the last part is the column name, and there are only 2parts. therefore...
803
            $field_name = $last_query_param_part;
804
            $model = \EE_Registry::instance()->load_model($query_param_parts[$number_of_parts - 2]);
805
        }
806
        try {
807
            return $model->field_settings_for($field_name, false);
0 ignored issues
show
Bug introduced by
It seems like $model is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
808
        } catch (EE_Error $e) {
809
            return null;
810
        }
811
    }
812
813
814
815
    /**
816
     * Returns true if $data can be easily represented in JSON.
817
     * Basically, objects and resources can't be represented in JSON easily.
818
     * @param mixed $data
819
     * @return bool
820
     */
821
    protected static function isRepresentableInJson($data)
822
    {
823
        return is_scalar($data)
824
               || is_array($data)
825
               || is_null($data);
826
    }
827
}
828