Completed
Branch BUG/11268/session-ticket-relea... (f9aa59)
by
unknown
13:30 queued 24s
created

getTimestampWithTimezoneOffset()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 33
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 23
nc 2
nop 3
dl 0
loc 33
rs 8.8571
c 0
b 0
f 0
1
<?php
2
namespace EventEspresso\core\libraries\rest_api;
3
4
use DomainException;
5
use EE_Capabilities;
6
use EE_Datetime_Field;
7
use EE_Error;
8
use EE_Infinite_Integer_Field;
9
use EE_Maybe_Serialized_Simple_HTML_Field;
10
use EE_Model_Field_Base;
11
use EE_Serialized_Text_Field;
12
use EEM_Base;
13
14
if (! defined('EVENT_ESPRESSO_VERSION')) {
15
    exit('No direct script access allowed');
16
}
17
18
19
20
/**
21
 * Class Model_Data_Translator
22
 * Class for translating data between the EE4 models and JSON.
23
 * Some of this class needs to interpret an incoming array of query params from
24
 * the REST API and prepare it for use by the models. Some of this code seems duplicated
25
 * from the models but it's anticipated to diverge (because providing parameters
26
 * in the REST API is sometimes more difficult than in PHP directly. Eg, providing an array like
27
 * array( 'where' => array( 'EVT_ID' => array( '<', 100 ) ) ) in PHP is easy, but in a query string it needs to look
28
 * like
29
 * "where[EVT_ID][]=<&where[EVT_ID][]=100" is less intuitive, so we may want
30
 * to allow REST API query parameters to diverge from the format accepted by models)
31
 *
32
 * @package               Event Espresso
33
 * @subpackage
34
 * @author                Mike Nelson
35
 * @since                 4.8.36
36
 */
37
class ModelDataTranslator
38
{
39
40
    /**
41
     * We used to use -1 for infinity in the rest api, but that's ambiguous for
42
     * fields that COULD contain -1; so we use null
43
     */
44
    const EE_INF_IN_REST = null;
45
46
47
48
    /**
49
     * Prepares a possible array of input values from JSON for use by the models
50
     *
51
     * @param EE_Model_Field_Base $field_obj
52
     * @param mixed                $original_value_maybe_array
53
     * @param string               $requested_version
54
     * @param string               $timezone_string treat values as being in this timezone
55
     * @return mixed
56
     * @throws RestException
57
     */
58
    public static function prepareFieldValuesFromJson(
59
        $field_obj,
60
        $original_value_maybe_array,
61
        $requested_version,
62
        $timezone_string = 'UTC'
63
    ) {
64
        if (is_array($original_value_maybe_array)
65
            && ! $field_obj instanceof EE_Serialized_Text_Field
66
        ) {
67
            $new_value_maybe_array = array();
68
            foreach ($original_value_maybe_array as $array_key => $array_item) {
69
                $new_value_maybe_array[$array_key] = ModelDataTranslator::prepareFieldValueFromJson(
70
                    $field_obj,
71
                    $array_item,
72
                    $requested_version,
73
                    $timezone_string
74
                );
75
            }
76
        } else {
77
            $new_value_maybe_array = ModelDataTranslator::prepareFieldValueFromJson(
78
                $field_obj,
79
                $original_value_maybe_array,
80
                $requested_version,
81
                $timezone_string
82
            );
83
        }
84
        return $new_value_maybe_array;
85
    }
86
87
88
89
    /**
90
     * Prepares an array of field values FOR use in JSON/REST API
91
     *
92
     * @param EE_Model_Field_Base $field_obj
93
     * @param mixed                $original_value_maybe_array
94
     * @param string               $request_version (eg 4.8.36)
95
     * @return array
96
     */
97
    public static function prepareFieldValuesForJson($field_obj, $original_value_maybe_array, $request_version)
98
    {
99
        if (is_array($original_value_maybe_array)) {
100
            $new_value = array();
101
            foreach ($original_value_maybe_array as $key => $value) {
102
                $new_value[$key] = ModelDataTranslator::prepareFieldValuesForJson($field_obj, $value, $request_version);
103
            }
104
        } else {
105
            $new_value = ModelDataTranslator::prepareFieldValueForJson(
106
                $field_obj,
107
                $original_value_maybe_array,
108
                $request_version
109
            );
110
        }
111
        return $new_value;
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
     * @throws DomainException
126
     * @throws EE_Error
127
     */
128
    public static function prepareFieldValueFromJson(
129
        $field_obj,
130
        $original_value,
131
        $requested_version,
132
        $timezone_string = 'UTC' // UTC
133
    ) {
134
        //check if they accidentally submitted an error value. If so throw an exception
135
        if (is_array($original_value)
136
            && isset($original_value['error_code'], $original_value['error_message'])) {
137
            throw new RestException(
138
                'rest_submitted_error_value',
139
                sprintf(
140
                    esc_html__(
141
                        'You tried to submit a JSON error object as a value for %1$s. That\'s not allowed.',
142
                        'event_espresso'
143
                    ),
144
                    $field_obj->get_name()
145
                ),
146
                array(
147
                    'status' => 400,
148
                )
149
            );
150
        }
151
        //double-check for serialized PHP. We never accept serialized PHP. No way Jose.
152
        ModelDataTranslator::throwExceptionIfContainsSerializedData($original_value);
153
        $timezone_string = $timezone_string !== '' ? $timezone_string : get_option('timezone_string', '');
154
        $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...
155
        //walk through the submitted data and double-check for serialized PHP. We never accept serialized PHP. No
156
        // way Jose.
157
        ModelDataTranslator::throwExceptionIfContainsSerializedData($original_value);
158
        if ($field_obj instanceof EE_Infinite_Integer_Field
159
            && in_array($original_value, array(null, ''), true)
160
        ) {
161
            $new_value = EE_INF;
162
        } elseif ($field_obj instanceof EE_Datetime_Field) {
163
            $new_value = rest_parse_date(
164
                self::getTimestampWithTimezoneOffset($original_value, $field_obj, $timezone_string)
165
            );
166
            if ($new_value === false) {
167
                throw new RestException(
168
                    'invalid_format_for_timestamp',
169
                    sprintf(
170
                        esc_html__(
171
                            'Timestamps received on a request as the value for Date and Time fields must be in %1$s/%2$s format.  The timestamp provided (%3$s) is not that format.',
172
                            'event_espresso'
173
                        ),
174
                        'RFC3339',
175
                        'ISO8601',
176
                        $original_value
177
                    ),
178
                    array(
179
                        'status' => 400
180
                    )
181
                );
182
            }
183
        } else {
184
            $new_value = $original_value;
185
        }
186
        return $new_value;
187
    }
188
189
190
    /**
191
     * This checks if the incoming timestamp has timezone information already on it and if it doesn't then adds timezone
192
     * information via details obtained from the host site.
193
     *
194
     * @param string            $original_timestamp
195
     * @param EE_Datetime_Field $datetime_field
196
     * @param                   $timezone_string
197
     * @return string
198
     * @throws DomainException
199
     */
200
    private static function getTimestampWithTimezoneOffset(
201
        $original_timestamp,
202
        EE_Datetime_Field $datetime_field,
203
        $timezone_string
204
    ) {
205
        //already have timezone information?
206
        if (preg_match('/Z|(\+|\-)(\d{2}:\d{2})/', $original_timestamp)) {
207
            //yes, we're ignoring the timezone.
208
            return $original_timestamp;
209
        }
210
        //need to append timezone
211
        list($offset_sign, $offset_secs) = self::parseTimezoneOffset(
212
            $datetime_field->get_timezone_offset(
213
                new \DateTimeZone($timezone_string),
214
                $original_timestamp
215
            )
216
        );
217
        $offset_string =
218
            str_pad(
219
                floor($offset_secs / HOUR_IN_SECONDS),
220
                2,
221
                '0',
222
                STR_PAD_LEFT
223
            )
224
            . ':'
225
            . str_pad(
226
                ($offset_secs % HOUR_IN_SECONDS) / MINUTE_IN_SECONDS,
227
                2,
228
                '0',
229
                STR_PAD_LEFT
230
            );
231
        return $original_timestamp . $offset_sign . $offset_string;
232
    }
233
234
235
236
    /**
237
     * Throws an exception if $data is a serialized PHP string (or somehow an actually PHP object, although I don't
238
     * think that can happen). If $data is an array, recurses into its keys and values
239
     * @param mixed $data
240
     * @throws RestException
241
     * @return void
242
     */
243
    public static function throwExceptionIfContainsSerializedData($data)
244
    {
245
        if (is_array($data)) {
246
            foreach ($data as $key => $value) {
247
                ModelDataTranslator::throwExceptionIfContainsSerializedData($key);
248
                ModelDataTranslator::throwExceptionIfContainsSerializedData($value);
249
            }
250
        } else {
251
            if (is_serialized($data) || is_object($data)) {
252
                throw new RestException(
253
                    'serialized_data_submission_prohibited',
254
                    esc_html__(
255
                        // @codingStandardsIgnoreStart
256
                        'You tried to submit a string of serialized text. Serialized PHP is prohibited over the EE4 REST API.',
257
                        // @codingStandardsIgnoreEnd
258
                        'event_espresso'
259
                    )
260
                );
261
            }
262
        }
263
    }
264
265
266
267
    /**
268
     * determines what's going on with them timezone strings
269
     *
270
     * @param int $timezone_offset
271
     * @return array
272
     */
273
    private static function parseTimezoneOffset($timezone_offset)
274
    {
275
        $first_char = substr((string)$timezone_offset, 0, 1);
276
        if ($first_char === '+' || $first_char === '-') {
277
            $offset_sign = $first_char;
278
            $offset_secs = substr((string)$timezone_offset, 1);
279
        } else {
280
            $offset_sign = '+';
281
            $offset_secs = $timezone_offset;
282
        }
283
        return array($offset_sign, $offset_secs);
284
    }
285
286
287
288
    /**
289
     * Prepares a field's value for display in the API
290
     *
291
     * @param EE_Model_Field_Base $field_obj
292
     * @param mixed                $original_value
293
     * @param string               $requested_version
294
     * @return mixed
295
     */
296
    public static function prepareFieldValueForJson($field_obj, $original_value, $requested_version)
297
    {
298
        if ($original_value === EE_INF) {
299
            $new_value = ModelDataTranslator::EE_INF_IN_REST;
300
        } elseif ($field_obj instanceof EE_Datetime_Field) {
301
            if (is_string($original_value)) {
302
                //did they submit a string of a unix timestamp?
303
                if (is_numeric($original_value)) {
304
                    $datetime_obj = new \DateTime();
305
                    $datetime_obj->setTimestamp((int)$original_value);
306
                } else {
307
                    //first, check if its a MySQL timestamp in GMT
308
                    $datetime_obj = \DateTime::createFromFormat('Y-m-d H:i:s', $original_value);
309
                }
310
                if (! $datetime_obj instanceof \DateTime) {
311
                    //so it's not a unix timestamp or a MySQL timestamp. Maybe its in the field's date/time format?
312
                    $datetime_obj = $field_obj->prepare_for_set($original_value);
313
                }
314
                $original_value = $datetime_obj;
315
            }
316
            if ($original_value instanceof \DateTime) {
317
                $new_value = $original_value->format('Y-m-d H:i:s');
318
            } elseif (is_int($original_value) || is_float($original_value)) {
319
                $new_value = date('Y-m-d H:i:s', $original_value);
320
            } elseif($original_value === null || $original_value === '') {
321
                $new_value = null;
322
            } else {
323
                //so it's not a datetime object, unix timestamp (as string or int),
324
                //MySQL timestamp, or even a string in the field object's format. So no idea what it is
325
                throw new \EE_Error(
326
                    sprintf(
327
                        esc_html__(
328
                        // @codingStandardsIgnoreStart
329
                            '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".',
330
                            // @codingStandardsIgnoreEnd
331
                            'event_espressso'
332
                        ),
333
                        $original_value,
334
                        $field_obj->get_name(),
335
                        $field_obj->get_model_name(),
336
                        $field_obj->get_time_format() . ' ' . $field_obj->get_time_format()
337
                    )
338
                );
339
            }
340
            $new_value = mysql_to_rfc3339($new_value);
341
        } else {
342
            $new_value = $original_value;
343
        }
344
        //are we about to send an object? just don't. We have no good way to represent it in JSON.
345
        // can't just check using is_object() because that missed PHP incomplete objects
346
        if (! ModelDataTranslator::isRepresentableInJson($new_value)) {
347
            $new_value = array(
348
                'error_code' => 'php_object_not_return',
349
                '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')
350
            );
351
        }
352
        return apply_filters(
353
            'FHEE__EventEspresso\core\libraries\rest_api\Model_Data_Translator__prepare_field_for_rest_api',
354
            $new_value,
355
            $field_obj,
356
            $original_value,
357
            $requested_version
358
        );
359
    }
360
361
362
363
    /**
364
     * Prepares condition-query-parameters (like what's in where and having) from
365
     * the format expected in the API to use in the models
366
     *
367
     * @param array     $inputted_query_params_of_this_type
368
     * @param EEM_Base $model
369
     * @param string    $requested_version
370
     * @param boolean $writing whether this data will be written to the DB, or if we're just building a query.
371
     *                         If we're writing to the DB, we don't expect any operators, or any logic query parameters,
372
     *                         and we also won't accept serialized data unless the current user has unfiltered_html.
373
     * @return array
374
     * @throws DomainException
375
     * @throws RestException
376
     * @throws EE_Error
377
     */
378
    public static function prepareConditionsQueryParamsForModels(
379
        $inputted_query_params_of_this_type,
380
        EEM_Base $model,
381
        $requested_version,
382
        $writing = false
383
    ) {
384
        $query_param_for_models = array();
385
        $valid_operators = $model->valid_operators();
386
        foreach ($inputted_query_params_of_this_type as $query_param_key => $query_param_value) {
387
            $is_gmt_datetime_field = false;
388
            $query_param_sans_stars = ModelDataTranslator::removeStarsAndAnythingAfterFromConditionQueryParamKey(
389
                $query_param_key
390
            );
391
            $field = ModelDataTranslator::deduceFieldFromQueryParam(
392
                $query_param_sans_stars,
393
                $model
394
            );
395
            //double-check is it a *_gmt field?
396
            if (! $field instanceof EE_Model_Field_Base
397
                && ModelDataTranslator::isGmtDateFieldName($query_param_sans_stars)
398
            ) {
399
                //yep, take off '_gmt', and find the field
400
                $query_param_key = ModelDataTranslator::removeGmtFromFieldName($query_param_sans_stars);
401
                $field = ModelDataTranslator::deduceFieldFromQueryParam(
402
                    $query_param_key,
403
                    $model
404
                );
405
                $timezone = 'UTC';
406
                $is_gmt_datetime_field = true;
407
            } elseif ($field instanceof EE_Datetime_Field) {
408
                //so it's not a GMT field. Set the timezone on the model to the default
409
                $timezone = \EEH_DTT_Helper::get_valid_timezone_string();
410
            } else {
411
                //just keep using what's already set for the timezone
412
                $timezone = $model->get_timezone();
413
            }
414
            if ($field instanceof EE_Model_Field_Base) {
415
                if (! $writing && is_array($query_param_value)) {
416 View Code Duplication
                    if (! \EEH_Array::is_array_numerically_and_sequentially_indexed($query_param_value)) {
417
                        if (defined('EE_REST_API_DEBUG_MODE') && EE_REST_API_DEBUG_MODE) {
418
                            throw new RestException(
419
                                'numerically_indexed_array_of_values_only',
420
                                sprintf(
421
                                    esc_html__(
422
                                        'The array provided for the parameter "%1$s" should be numerically indexed.',
423
                                        'event_espresso'
424
                                    ),
425
                                    $query_param_key
426
                                ),
427
                                array(
428
                                    'status' => 400,
429
                                )
430
                            );
431
                        }
432
                    }
433
                    //did they specify an operator?
434
                    if (isset($query_param_value[0])
435
                        && isset($valid_operators[$query_param_value[0]])
436
                    ) {
437
                        $op = $query_param_value[0];
438
                        $translated_value = array($op);
439
                        if (array_key_exists($op, $model->valid_in_style_operators())
440
                            && isset($query_param_value[1])
441
                            && ! isset($query_param_value[2])
442
                        ) {
443
                            $translated_value[] = ModelDataTranslator::prepareFieldValuesFromJson(
444
                                $field,
445
                                $query_param_value[1],
446
                                $requested_version,
447
                                $timezone
448
                            );
449
                        } elseif (array_key_exists($op, $model->valid_between_style_operators())
450
                            && isset($query_param_value[1], $query_param_value[2])
451
                            && !isset($query_param_value[3])
452
                        ) {
453
                            $translated_value[] = ModelDataTranslator::prepareFieldValuesFromJson(
454
                                $field,
455
                                $query_param_value[1],
456
                                $requested_version,
457
                                $timezone
458
                            );
459
                            $translated_value[] = ModelDataTranslator::prepareFieldValuesFromJson(
460
                                $field,
461
                                $query_param_value[2],
462
                                $requested_version,
463
                                $timezone
464
                            );
465
                        } elseif (array_key_exists($op, $model->valid_like_style_operators())
466
                            && isset($query_param_value[1])
467
                            && ! isset($query_param_value[2])
468
                        ) {
469
                            //we want to leave this value mostly-as-is (eg don't force it to be a float
470
                            //or a boolean or an enum value. Leave it as-is with wildcards etc)
471
                            //but do verify it at least doesn't have any serialized data
472
                            ModelDataTranslator::throwExceptionIfContainsSerializedData($query_param_value[1]);
473
                            $translated_value[] = $query_param_value[1];
474
                        } 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...
475
                            && !isset($query_param_value[1])) {
476
                            //no arguments should have been provided, so don't look for any
477
                        } elseif (isset($query_param_value[1])
478
                            && !isset($query_param_value[2])
479
                            && ! array_key_exists(
480
                                $op,
481
                                array_merge(
482
                                    $model->valid_in_style_operators(),
483
                                    $model->valid_null_style_operators(),
484
                                    $model->valid_like_style_operators(),
485
                                    $model->valid_between_style_operators()
486
                                )
487
                            )
488
                        ) {
489
                            //it's a valid operator, but none of the exceptions. Treat it normally.
490
                            $translated_value[] = ModelDataTranslator::prepareFieldValuesFromJson(
491
                                $field,
492
                                $query_param_value[1],
493
                                $requested_version,
494
                                $timezone
495
                            );
496 View Code Duplication
                        } else {
497
                            //so they provided a valid operator, but wrong number of arguments
498
                            if (defined('EE_REST_API_DEBUG_MODE') && EE_REST_API_DEBUG_MODE) {
499
                                throw new RestException(
500
                                    'wrong_number_of_arguments',
501
                                    sprintf(
502
                                        esc_html__(
503
                                            'The operator you provided, "%1$s" had the wrong number of arguments',
504
                                            'event_espresso'
505
                                        ),
506
                                        $op
507
                                    ),
508
                                    array(
509
                                        'status' => 400,
510
                                    )
511
                                );
512
                            }
513
                            $translated_value = null;
514
                        }
515 View Code Duplication
                    } else {
516
                        //so they didn't provide a valid operator
517
                        if (defined('EE_REST_API_DEBUG_MODE') && EE_REST_API_DEBUG_MODE) {
518
                            throw new RestException(
519
                                'invalid_operator',
520
                                sprintf(
521
                                    esc_html__(
522
                                        'You provided an invalid parameter, with key "%1$s" and value "%2$s"',
523
                                        'event_espresso'
524
                                    ),
525
                                    $query_param_key,
526
                                    $query_param_value
527
                                ),
528
                                array(
529
                                    'status' => 400,
530
                                )
531
                            );
532
                        }
533
                        //if we aren't in debug mode, then just try our best to fulfill the user's request
534
                        $translated_value = null;
535
                    }
536
                } else {
537
                    $translated_value = ModelDataTranslator::prepareFieldValueFromJson(
538
                        $field,
539
                        $query_param_value,
540
                        $requested_version,
541
                        $timezone
542
                    );
543
                }
544
                if (
545
                    (isset($query_param_for_models[$query_param_key]) && $is_gmt_datetime_field)
546
                    ||
547
                    $translated_value === null
548
                ) {
549
                    //they have already provided a non-gmt field, ignore the gmt one. That's what WP core
550
                    //currently does (they might change it though). See https://core.trac.wordpress.org/ticket/39954
551
                    //OR we couldn't create a translated value from their input
552
                    continue;
553
                }
554
                $query_param_for_models[$query_param_key] = $translated_value;
555
            } else {
556
                //so this param doesn't correspond to a field eh?
557
                if ($writing) {
558
                    //always tell API clients about invalid parameters when they're creating data. Otherwise,
559
                    //they are probably going to create invalid data
560
                    throw new RestException(
561
                        'invalid_field',
562
                        sprintf(
563
                            esc_html__('You have provided an invalid parameter: "%1$s"', 'event_espresso'),
564
                            $query_param_key
565
                        )
566
                    );
567
                } else {
568
                    //so it's not for a field, is it a logic query param key?
569
                    if (in_array(
570
                        $query_param_sans_stars,
571
                        $model->logic_query_param_keys()
572
                    )) {
573
                        $query_param_for_models[$query_param_key] = ModelDataTranslator::prepareConditionsQueryParamsForModels(
574
                            $query_param_value,
575
                            $model,
576
                            $requested_version
577
                        );
578 View Code Duplication
                    } elseif (defined('EE_REST_API_DEBUG_MODE') && EE_REST_API_DEBUG_MODE) {
579
                        //only tell API clients they got it wrong if we're in debug mode
580
                        //otherwise try our best ot fulfill their request by ignoring this invalid data
581
                        throw new RestException(
582
                            'invalid_parameter',
583
                            sprintf(
584
                                esc_html__(
585
                                    'You provided an invalid parameter, with key "%1$s"',
586
                                    'event_espresso'
587
                                ),
588
                                $query_param_sans_stars
589
                            ),
590
                            array(
591
                                'status' => 400,
592
                            )
593
                        );
594
                    }
595
                }
596
            }
597
        }
598
        return $query_param_for_models;
599
    }
600
601
602
603
    /**
604
     * Mostly checks if the last 4 characters are "_gmt", indicating its a
605
     * gmt date field name
606
     *
607
     * @param string $field_name
608
     * @return boolean
609
     */
610
    public static function isGmtDateFieldName($field_name)
611
    {
612
        return substr(
613
            ModelDataTranslator::removeStarsAndAnythingAfterFromConditionQueryParamKey($field_name),
614
            -4,
615
            4
616
        ) === '_gmt';
617
    }
618
619
620
621
    /**
622
     * Removes the last "_gmt" part of a field name (and if there is no "_gmt" at the end, leave it alone)
623
     *
624
     * @param string $field_name
625
     * @return string
626
     */
627
    public static function removeGmtFromFieldName($field_name)
628
    {
629
        if (! ModelDataTranslator::isGmtDateFieldName($field_name)) {
630
            return $field_name;
631
        }
632
        $query_param_sans_stars = ModelDataTranslator::removeStarsAndAnythingAfterFromConditionQueryParamKey(
633
            $field_name
634
        );
635
        $query_param_sans_gmt_and_sans_stars = substr(
636
            $query_param_sans_stars,
637
            0,
638
            strrpos(
639
                $field_name,
640
                '_gmt'
641
            )
642
        );
643
        return str_replace($query_param_sans_stars, $query_param_sans_gmt_and_sans_stars, $field_name);
644
    }
645
646
647
648
    /**
649
     * Takes a field name from the REST API and prepares it for the model querying
650
     *
651
     * @param string $field_name
652
     * @return string
653
     */
654
    public static function prepareFieldNameFromJson($field_name)
655
    {
656
        if (ModelDataTranslator::isGmtDateFieldName($field_name)) {
657
            return ModelDataTranslator::removeGmtFromFieldName($field_name);
658
        }
659
        return $field_name;
660
    }
661
662
663
664
    /**
665
     * Takes array of field names from REST API and prepares for models
666
     *
667
     * @param array $field_names
668
     * @return array of field names (possibly include model prefixes)
669
     */
670
    public static function prepareFieldNamesFromJson(array $field_names)
671
    {
672
        $new_array = array();
673
        foreach ($field_names as $key => $field_name) {
674
            $new_array[$key] = ModelDataTranslator::prepareFieldNameFromJson($field_name);
675
        }
676
        return $new_array;
677
    }
678
679
680
681
    /**
682
     * Takes array where array keys are field names (possibly with model path prefixes)
683
     * from the REST API and prepares them for model querying
684
     *
685
     * @param array $field_names_as_keys
686
     * @return array
687
     */
688
    public static function prepareFieldNamesInArrayKeysFromJson(array $field_names_as_keys)
689
    {
690
        $new_array = array();
691
        foreach ($field_names_as_keys as $field_name => $value) {
692
            $new_array[ModelDataTranslator::prepareFieldNameFromJson($field_name)] = $value;
693
        }
694
        return $new_array;
695
    }
696
697
698
699
    /**
700
     * Prepares an array of model query params for use in the REST API
701
     *
702
     * @param array     $model_query_params
703
     * @param EEM_Base $model
704
     * @param string    $requested_version eg "4.8.36". If null is provided, defaults to the latest release of the EE4
705
     *                                     REST API
706
     * @return array which can be passed into the EE4 REST API when querying a model resource
707
     * @throws EE_Error
708
     */
709
    public static function prepareQueryParamsForRestApi(
710
        array $model_query_params,
711
        EEM_Base $model,
712
        $requested_version = null
713
    ) {
714
        if ($requested_version === null) {
715
            $requested_version = \EED_Core_Rest_Api::latest_rest_api_version();
716
        }
717
        $rest_query_params = $model_query_params;
718 View Code Duplication
        if (isset($model_query_params[0])) {
719
            $rest_query_params['where'] = ModelDataTranslator::prepareConditionsQueryParamsForRestApi(
720
                $model_query_params[0],
721
                $model,
722
                $requested_version
723
            );
724
            unset($rest_query_params[0]);
725
        }
726 View Code Duplication
        if (isset($model_query_params['having'])) {
727
            $rest_query_params['having'] = ModelDataTranslator::prepareConditionsQueryParamsForRestApi(
728
                $model_query_params['having'],
729
                $model,
730
                $requested_version
731
            );
732
        }
733
        return apply_filters(
734
            'FHEE__EventEspresso\core\libraries\rest_api\Model_Data_Translator__prepare_query_params_for_rest_api',
735
            $rest_query_params,
736
            $model_query_params,
737
            $model,
738
            $requested_version
739
        );
740
    }
741
742
743
744
    /**
745
     * Prepares all the sub-conditions query parameters (eg having or where conditions) for use in the rest api
746
     *
747
     * @param array     $inputted_query_params_of_this_type eg like the "where" or "having" conditions query params
748
     *                                                      passed into EEM_Base::get_all()
749
     * @param EEM_Base $model
750
     * @param string    $requested_version                  eg "4.8.36"
751
     * @return array ready for use in the rest api query params
752
     * @throws EE_Error
753
     * @throws ObjectDetectedException if somehow a PHP object were in the query params' values,
754
     *                                     (which would be really unusual)
755
     */
756
    public static function prepareConditionsQueryParamsForRestApi(
757
        $inputted_query_params_of_this_type,
758
        EEM_Base $model,
759
        $requested_version
760
    ) {
761
        $query_param_for_models = array();
762
        foreach ($inputted_query_params_of_this_type as $query_param_key => $query_param_value) {
763
            $field = ModelDataTranslator::deduceFieldFromQueryParam(
764
                ModelDataTranslator::removeStarsAndAnythingAfterFromConditionQueryParamKey($query_param_key),
765
                $model
766
            );
767
            if ($field instanceof EE_Model_Field_Base) {
768
                //did they specify an operator?
769
                if (is_array($query_param_value)) {
770
                    $op = $query_param_value[0];
771
                    $translated_value = array($op);
772
                    if (isset($query_param_value[1])) {
773
                        $value = $query_param_value[1];
774
                        $translated_value[1] = ModelDataTranslator::prepareFieldValuesForJson(
775
                            $field,
776
                            $value,
777
                            $requested_version
778
                        );
779
                    }
780
                } else {
781
                    $translated_value = ModelDataTranslator::prepareFieldValueForJson(
782
                        $field,
783
                        $query_param_value,
784
                        $requested_version
785
                    );
786
                }
787
                $query_param_for_models[$query_param_key] = $translated_value;
788
            } else {
789
                //so it's not for a field, assume it's a logic query param key
790
                $query_param_for_models[$query_param_key] = ModelDataTranslator::prepareConditionsQueryParamsForRestApi(
791
                    $query_param_value,
792
                    $model,
793
                    $requested_version
794
                );
795
            }
796
        }
797
        return $query_param_for_models;
798
    }
799
800
801
802
    /**
803
     * @param $condition_query_param_key
804
     * @return string
805
     */
806 View Code Duplication
    public static function removeStarsAndAnythingAfterFromConditionQueryParamKey($condition_query_param_key)
807
    {
808
        $pos_of_star = strpos($condition_query_param_key, '*');
809
        if ($pos_of_star === false) {
810
            return $condition_query_param_key;
811
        } else {
812
            $condition_query_param_sans_star = substr($condition_query_param_key, 0, $pos_of_star);
813
            return $condition_query_param_sans_star;
814
        }
815
    }
816
817
818
819
    /**
820
     * Takes the input parameter and finds the model field that it indicates.
821
     *
822
     * @param string    $query_param_name like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
823
     * @param EEM_Base $model
824
     * @return EE_Model_Field_Base
825
     * @throws EE_Error
826
     */
827
    public static function deduceFieldFromQueryParam($query_param_name, EEM_Base $model)
828
    {
829
        //ok, now proceed with deducing which part is the model's name, and which is the field's name
830
        //which will help us find the database table and column
831
        $query_param_parts = explode('.', $query_param_name);
832
        if (empty($query_param_parts)) {
833
            throw new EE_Error(
834
                sprintf(
835
                    __(
836
                        '_extract_column_name is empty when trying to extract column and table name from %s',
837
                        'event_espresso'
838
                    ),
839
                    $query_param_name
840
                )
841
            );
842
        }
843
        $number_of_parts = count($query_param_parts);
844
        $last_query_param_part = $query_param_parts[count($query_param_parts) - 1];
845
        if ($number_of_parts === 1) {
846
            $field_name = $last_query_param_part;
847
        } else {// $number_of_parts >= 2
848
            //the last part is the column name, and there are only 2parts. therefore...
849
            $field_name = $last_query_param_part;
850
            $model = \EE_Registry::instance()->load_model($query_param_parts[$number_of_parts - 2]);
851
        }
852
        try {
853
            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...
854
        } catch (EE_Error $e) {
855
            return null;
856
        }
857
    }
858
859
860
861
    /**
862
     * Returns true if $data can be easily represented in JSON.
863
     * Basically, objects and resources can't be represented in JSON easily.
864
     * @param mixed $data
865
     * @return bool
866
     */
867
    protected static function isRepresentableInJson($data)
868
    {
869
        return is_scalar($data)
870
               || is_array($data)
871
               || is_null($data);
872
    }
873
}
874