Completed
Branch FET/simplify-rest-api-passing-... (a03e49)
by
unknown
28:07 queued 18:54
created
core/libraries/rest_api/RestIncomingQueryParamMetadata.php 1 patch
Indentation   +675 added lines, -675 removed lines patch added patch discarded remove patch
@@ -26,681 +26,681 @@
 block discarded – undo
26 26
  */
27 27
 class RestIncomingQueryParamMetadata
28 28
 {
29
-    private $query_param_key;
30
-    private $query_param_value;
31
-    /**
32
-     * @var RestIncomingQueryParamContext
33
-     */
34
-    private $context;
35
-
36
-    /**
37
-     * @var EE_Model_Field_Base|null
38
-     */
39
-    private $field;
40
-
41
-    /**
42
-     * @var string same as $query_param_key but has the * and anything after it removed
43
-     */
44
-    private $query_param_key_sans_stars;
45
-
46
-    /**
47
-     * @var string for timezone or timezone offset
48
-     */
49
-    private $timezone;
50
-
51
-    /**
52
-     * @var boolean if the field in $query_param_key is for a GMT field (eg `EVT_modified_gmt`)
53
-     */
54
-    private $is_gmt_field = false;
55
-
56
-    /**
57
-     * RestIncomingQueryParamMetadata constructor.
58
-     * You probably want to call
59
-     * @param string $query_param_key
60
-     * @param string $query_param_value
61
-     * @param RestIncomingQueryParamContext $context
62
-     */
63
-    public function __construct($query_param_key, $query_param_value, RestIncomingQueryParamContext $context)
64
-    {
65
-        $this->query_param_key = $query_param_key;
66
-        $this->query_param_value = $query_param_value;
67
-        $this->context = $context;
68
-        $this->determineFieldAndTimezone();
69
-    }
70
-
71
-    /**
72
-     * Gets the query parameter key. This may have been modified (see setQueryParamValue())
73
-     * @return string
74
-     */
75
-    public function getQueryParamKey()
76
-    {
77
-        return $this->query_param_key;
78
-    }
79
-
80
-    /**
81
-     * Modifies the query parameter key passed in (Eg this is done when rewriting the simplified specified operator REST
82
-     * query parameters into the legacy structure)
83
-     * @param string|array|int|float $query_param_value
84
-     */
85
-    private function setQueryParamValue($query_param_value)
86
-    {
87
-        $this->query_param_value = $query_param_value;
88
-    }
89
-
90
-    /**
91
-     * Gets the original query parameter value passed in.
92
-     * @return string
93
-     */
94
-    public function getQueryParamValue()
95
-    {
96
-        return $this->query_param_value;
97
-    }
98
-
99
-    /**
100
-     * Gets the context object.
101
-     * @return RestIncomingQueryParamContext
102
-     */
103
-    public function getContext()
104
-    {
105
-        return $this->context;
106
-    }
107
-
108
-    /**
109
-     * Sets the query parameter key. This may be used to rewrite a key into its non-GMT alternative.
110
-     * @param string $query_param_key
111
-     */
112
-    private function setQueryParamKey($query_param_key)
113
-    {
114
-        $this->query_param_key = $query_param_key;
115
-    }
116
-
117
-    /**
118
-     * Gets the field the query parameter key indicated. This may be null (in cases where the query parameter key
119
-     * did not indicate a field, eg if it were `OR`).
120
-     * @return EE_Model_Field_Base|null
121
-     */
122
-    public function getField()
123
-    {
124
-        return $this->field;
125
-    }
126
-
127
-    /**
128
-     * Gets the query parameter key (with the star and everything afterwards removed).
129
-     * @return string
130
-     */
131
-    public function getQueryParamKeySansStars()
132
-    {
133
-        return $this->query_param_key_sans_stars;
134
-    }
135
-
136
-    /**
137
-     * Gets the timezone associated with this model (the site timezone, except for GMT datetime fields).
138
-     * @return string
139
-     */
140
-    public function getTimezone()
141
-    {
142
-        return $this->timezone;
143
-    }
144
-
145
-    /**
146
-     * Returns whether or not this is a GMT field
147
-     * @return boolean
148
-     */
149
-    public function isGmtField()
150
-    {
151
-        return $this->is_gmt_field;
152
-    }
153
-
154
-    /**
155
-     * Sets the field indicated by the query parameter key (might be null).
156
-     * @param EE_Model_Field_Base|null $field
157
-     */
158
-    private function setField(EE_Model_Field_Base $field = null)
159
-    {
160
-        $this->field = $field;
161
-    }
162
-
163
-    /**
164
-     * Sets the query parameter key-with-stars-removed.
165
-     * @param string $query_param_key_sans_stars
166
-     */
167
-    private function setQueryParamKeySansStars($query_param_key_sans_stars)
168
-    {
169
-        $this->query_param_key_sans_stars = $query_param_key_sans_stars;
170
-    }
171
-
172
-    /**
173
-     * Sets the timezone (this could be a timezeon offset string).
174
-     * @param string $timezone
175
-     */
176
-    private function setTimezone($timezone)
177
-    {
178
-        $this->timezone = $timezone;
179
-    }
180
-
181
-    /**
182
-     * @param mixed $is_gmt_field
183
-     */
184
-    private function setIsGmtField($is_gmt_field)
185
-    {
186
-        $this->is_gmt_field = $is_gmt_field;
187
-    }
188
-
189
-    /**
190
-     * Determines what field, query param name, and query param name without stars, and timezone to use.
191
-     * @since $VID:$
192
-     * @type EE_Model_Field_Base $field
193
-     * @return void {
194
-     * @throws EE_Error
195
-     * @throws InvalidDataTypeException
196
-     * @throws InvalidInterfaceException
197
-     * @throws InvalidArgumentException
198
-     */
199
-    private function determineFieldAndTimezone()
200
-    {
201
-        $this->setQueryParamKeySansStars(ModelDataTranslator::removeStarsAndAnythingAfterFromConditionQueryParamKey(
202
-            $this->getQueryParamKey()
203
-        ));
204
-        $this->setField(ModelDataTranslator::deduceFieldFromQueryParam(
205
-            $this->getQueryParamKeySansStars(),
206
-            $this->getContext()->getModel()
207
-        ));
208
-        // double-check is it a *_gmt field?
209
-        if (!$this->getField() instanceof EE_Model_Field_Base
210
-            && ModelDataTranslator::isGmtDateFieldName($this->getQueryParamKeySansStars())
211
-        ) {
212
-            // yep, take off '_gmt', and find the field
213
-            $this->setQueryParamKey(ModelDataTranslator::removeGmtFromFieldName($this->getQueryParamKeySansStars()));
214
-            $this->setField(ModelDataTranslator::deduceFieldFromQueryParam(
215
-                $this->getQueryParamKey(),
216
-                $this->context->getModel()
217
-            ));
218
-            $this->setTimezone('UTC');
219
-            $this->setIsGmtField(true);
220
-        } elseif ($this->getField() instanceof EE_Datetime_Field) {
221
-            // so it's not a GMT field. Set the timezone on the model to the default
222
-            $this->setTimezone(EEH_DTT_Helper::get_valid_timezone_string());
223
-        } else {
224
-            // just keep using what's already set for the timezone
225
-            $this->setTimezone($this->context->getModel()->get_timezone());
226
-        }
227
-    }
228
-
229
-    /**
230
-     * Given a ton of input, determines the value to use for the models.
231
-     * @since $VID:$
232
-     * @return array|null
233
-     * @throws DomainException
234
-     * @throws EE_Error
235
-     * @throws RestException
236
-     * @throws DomainException
237
-     */
238
-    public function determineConditionsQueryParameterValue()
239
-    {
240
-        if ($this->valueIsArrayDuringRead()) {
241
-            return $this->determineModelValueGivenRestInputArray();
242
-        }
243
-        return ModelDataTranslator::prepareFieldValueFromJson(
244
-            $this->getField(),
245
-            $this->getQueryParamValue(),
246
-            $this->getContext()->getRequestedVersion(),
247
-            $this->getTimezone()
248
-        );
249
-    }
250
-
251
-    /**
252
-     * Given that the array value provided was itself an array, handles finding the correct value to pass to the model.
253
-     * @since $VID:$
254
-     * @return array|null
255
-     * @throws RestException
256
-     */
257
-    private function determineModelValueGivenRestInputArray()
258
-    {
259
-        $this->transformSimplifiedSpecifiedOperatorSyntaxIntoStandardSyntax();
260
-        // did they specify an operator?
261
-        if ($this->valueIsLegacySpecifiedOperator()) {
262
-            $query_param_value = $this->getQueryParamValue();
263
-            $sub_array_key = $query_param_value[0];
264
-            $translated_value = array($sub_array_key);
265
-            if ($this->operatorIsNAry($sub_array_key)) {
266
-                $translated_value[] = $this->prepareValuesFromJson($query_param_value[1]);
267
-            } elseif ($this->operatorIsTernary($sub_array_key)) {
268
-                $translated_value[] = array(
269
-                    $this->prepareValuesFromJson($query_param_value[1][0]),
270
-                    $this->prepareValuesFromJson($query_param_value[1][1])
271
-                );
272
-            } elseif ($this->operatorIsLike($sub_array_key)) {
273
-                // we want to leave this value mostly-as-is (eg don't force it to be a float
274
-                // or a boolean or an enum value. Leave it as-is with wildcards etc)
275
-                // but do verify it at least doesn't have any serialized data
276
-                ModelDataTranslator::throwExceptionIfContainsSerializedData($query_param_value[1]);
277
-                $translated_value[] = $query_param_value[1];
278
-            } elseif ($this->operatorIsUnary($sub_array_key)) {
279
-                // no arguments should have been provided, so don't look for any
280
-            } elseif ($this->operatorisBinary($sub_array_key)) {
281
-                // it's a valid operator, but none of the exceptions. Treat it normally.
282
-                $translated_value[] = $this->prepareValuesFromJson($query_param_value[1]);
283
-            } else {
284
-                // so they provided a valid operator, but wrong number of arguments
285
-                $this->throwWrongNumberOfArgsExceptionIfDebugging($sub_array_key);
286
-                $translated_value = null;
287
-            }
288
-        } else {
289
-            // so they didn't provide a valid operator
290
-            // if we aren't in debug mode, then just try our best to fulfill the user's request
291
-            $this->throwInvalidOperatorExceptionIfDebugging();
292
-            $translated_value = null;
293
-        }
294
-        return $translated_value;
295
-    }
296
-
297
-    /**
298
-     * Returns if this request is a "read" request and the value provided was an array.
299
-     * This will indicate is such things as `array('<', 123)` and `array('IN', array(1,2,3))` are acceptable or not.
300
-     * @since $VID:$
301
-     * @return boolean
302
-     */
303
-    private function valueIsArrayDuringRead()
304
-    {
305
-        return !$this->getContext()->isWriting() && is_array($this->getQueryParamValue());
306
-    }
307
-
308
-    /**
309
-     * Returns if the value provided was an associative array (we should have already verified it's an array of some
310
-     * sort). If the value is an associative array, it had better be in the simplified specified operator structure.
311
-     * @since $VID:$
312
-     * @return boolean
313
-     */
314
-    private function valueIsAssociativeArray()
315
-    {
316
-        return !EEH_Array::is_array_numerically_and_sequentially_indexed($this->getQueryParamValue());
317
-    }
318
-
319
-    /**
320
-     * Checks if the array value is itself an array that fits into the simplified specified operator structure
321
-     * (eg `array('!=' => 123)`).
322
-     * @since $VID:$
323
-     * @return boolean
324
-     */
325
-    private function valueIsSimplifiedSpecifiedOperator()
326
-    {
327
-        return count($this->getQueryParamValue()) === 1
328
-            && array_key_exists(
329
-                key($this->getQueryParamValue()),
330
-                $this->getContext()->getModel()->valid_operators()
331
-            );
332
-    }
333
-
334
-    /**
335
-     * Throws an exception if the sub-value is an array (eg `array('!=' => array())`). It needs to just be a string,
336
-     * of either comma-separated-values, or a JSON array.
337
-     * @since $VID:$
338
-     * @param $sub_array_key
339
-     * @param $sub_array_value
340
-     * @throws RestException
341
-     */
342
-    private function assertSubValueIsntArray($sub_array_key, $sub_array_value)
343
-    {
344
-        if (is_array($sub_array_value) && EED_Core_Rest_Api::debugMode()) {
345
-            throw new RestException(
346
-                'csv_or_json_string_only',
347
-                sprintf(
348
-                    /* translators: 1: variable name*/
349
-                    esc_html__(
350
-                        'The value provided for the operator "%1$s" should be comma-separated value string or a JSON array.',
351
-                        'event_espresso'
352
-                    ),
353
-                    $sub_array_key
354
-                ),
355
-                array(
356
-                    'status' => 400,
357
-                )
358
-            );
359
-        }
360
-    }
361
-
362
-    /**
363
-     * Determines if the sub-array key is an operator taking 3 or more operators.
364
-     * @since $VID:$
365
-     * @param $sub_array_key
366
-     * @return boolean
367
-     */
368
-    private function subArrayKeyIsNonBinaryOperator($sub_array_key)
369
-    {
370
-        return array_key_exists(
371
-            $sub_array_key,
372
-            array_merge(
373
-                $this->getContext()->getModel()->valid_in_style_operators(),
374
-                $this->getContext()->getModel()->valid_between_style_operators()
375
-            )
376
-        );
377
-    }
378
-
379
-    /**
380
-     * Given that the $sub_array_key is a string, checks if it's an operator taking only 1 argument.
381
-     * @since $VID:$
382
-     * @param string $sub_array_key
383
-     * @return boolean
384
-     */
385
-    private function subArrayKeyIsUnaryOperator($sub_array_key)
386
-    {
387
-        return array_key_exists(
388
-            $sub_array_key,
389
-            $this->getContext()->getModel()->valid_null_style_operators()
390
-        );
391
-    }
392
-
393
-    /**
394
-     * Parses the $sub_array_value string into an array (given it could either be a comma-separated-list or a JSON
395
-     * array). eg `"1,2,3"` or `"[1,2,3]"` into `array(1,2,3)`.
396
-     * @since $VID:$
397
-     * @param $sub_array_value
398
-     * @return array|mixed|object
399
-     */
400
-    private function extractQuickStyleSpecifiedOperatorValue($sub_array_value)
401
-    {
402
-        // the value should be JSON or CSV
403
-        $values = json_decode($sub_array_value);
404
-        if (!is_array($values)) {
405
-            $values = array_filter(
406
-                array_map(
407
-                    'trim',
408
-                    explode(
409
-                        ',',
410
-                        $sub_array_value
411
-                    )
412
-                )
413
-            );
414
-        }
415
-        return $values;
416
-    }
417
-
418
-    /**
419
-     * Throws an exception if the value isn't a simplified specified operator (only called when we expect that).
420
-     * @since $VID:$
421
-     * @throws RestException
422
-     */
423
-    private function assertSimplifiedSpecifiedOperator()
424
-    {
425
-        if (!$this->valueIsSimplifiedSpecifiedOperator() && EED_Core_Rest_Api::debugMode()) {
426
-            throw new RestException(
427
-                'numerically_indexed_array_of_values_only',
428
-                sprintf(
429
-                    /* translators: 1: variable name*/
430
-                    esc_html__(
431
-                        'The array provided for the parameter "%1$s" should be numerically indexed.',
432
-                        'event_espresso'
433
-                    ),
434
-                    $this->getQueryParamKey()
435
-                ),
436
-                array(
437
-                    'status' => 400,
438
-                )
439
-            );
440
-        }
441
-    }
442
-
443
-    /**
444
-     * If query_param_value were in the simplified specific operator structure, change it into the legacy structure.
445
-     * @since $VID:$
446
-     * @throws RestException
447
-     */
448
-    private function transformSimplifiedSpecifiedOperatorSyntaxIntoStandardSyntax()
449
-    {
450
-        if ($this->valueIsAssociativeArray()) {
451
-            $this->assertSimplifiedSpecifiedOperator();
452
-            $query_param_value = $this->getQueryParamValue();
453
-            $sub_array_value = reset($query_param_value);
454
-            $sub_array_key = key($query_param_value);
455
-            $this->assertSubValueIsntArray($sub_array_key, $sub_array_value);
456
-            // they're doing something like "&where[EVT_ID][IN]=1,2,3" or "&where[EVT_ID][>]=5"
457
-            if ($this->subArrayKeyIsNonBinaryOperator($sub_array_key)) {
458
-                $this->setQueryParamValue(array(
459
-                    $sub_array_key,
460
-                    $this->extractQuickStyleSpecifiedOperatorValue($sub_array_value)
461
-                ));
462
-            } elseif ($this->subArrayKeyIsUnaryOperator($sub_array_key)) {
463
-                $this->setQueryParamValue(array($sub_array_key));
464
-            } else {
465
-                $this->setQueryParamValue(array($sub_array_key, $sub_array_value));
466
-            }
467
-        }
468
-    }
469
-
470
-    /**
471
-     * Returns true is the value is an array using the legacy structure to specify the operator. Eg `array('!=',123)`.
472
-     * @since $VID:$
473
-     * @return boolean
474
-     */
475
-    private function valueIsLegacySpecifiedOperator()
476
-    {
477
-        $valid_operators = $this->getContext()->getModel()->valid_operators();
478
-        $query_param_value = $this->getQueryParamValue();
479
-        return isset($query_param_value[0])
480
-            && isset($valid_operators[ $query_param_value[0] ]);
481
-    }
482
-
483
-    /**
484
-     * Returns true if the value specified operator accepts arbitrary number of arguments, like "IN".
485
-     * @since $VID:$
486
-     * @param $operator
487
-     * @return boolean
488
-     */
489
-    private function operatorIsNAry($operator)
490
-    {
491
-        $valueArray = $this->getQueryParamValue();
492
-        return array_key_exists(
493
-            $operator,
494
-            $this->getContext()->getModel()->valid_in_style_operators()
495
-        )
496
-            && isset($valueArray[1])
497
-            && is_array($valueArray[1])
498
-            && !isset($valueArray[2]);
499
-    }
500
-
501
-    /**
502
-     * Returns true if the operator accepts 3 arguments (eg "BETWEEN").
503
-     * So we're looking for a value that looks like
504
-     * `array('BETWEEN', array('2015-01-01T00:00:00', '2016-01-01T00:00:00'))`.
505
-     * @since $VID:$
506
-     * @param $operator
507
-     * @return boolean
508
-     */
509
-    private function operatorIsTernary($operator)
510
-    {
511
-        $query_param_value = $this->getQueryParamValue();
512
-        return array_key_exists($operator, $this->getContext()->getModel()->valid_between_style_operators())
513
-            && isset($query_param_value[1])
514
-            && is_array($query_param_value[1])
515
-            && isset($query_param_value[1][0], $query_param_value[1][1])
516
-            && !isset($query_param_value[1][2])
517
-            && !isset($query_param_value[2]);
518
-    }
519
-
520
-    /**
521
-     * Returns true if the operator is a similar to LIKE, indicating the value may have wildcards we should leave alone.
522
-     * @since $VID:$
523
-     * @param $operator
524
-     * @return boolean
525
-     */
526
-    private function operatorIsLike($operator)
527
-    {
528
-        $query_param_value = $this->getQueryParamValue();
529
-        return array_key_exists($operator, $this->getContext()->getModel()->valid_like_style_operators())
530
-            && isset($query_param_value[1])
531
-            && !isset($query_param_value[2]);
532
-    }
533
-
534
-    /**
535
-     * Returns true if the operator only takes one argument (eg it's like `IS NULL`).
536
-     * @since $VID:$
537
-     * @param $operator
538
-     * @return boolean
539
-     */
540
-    private function operatorIsUnary($operator)
541
-    {
542
-        $query_param_value = $this->getQueryParamValue();
543
-        return array_key_exists($operator, $this->getContext()->getModel()->valid_null_style_operators())
544
-            && !isset($query_param_value[1]);
545
-    }
546
-
547
-    /**
548
-     * Returns true if the operator specified is a binary opeator (eg `=`, `!=`)
549
-     * @since $VID:$
550
-     * @param $operator
551
-     * @return boolean
552
-     */
553
-    private function operatorisBinary($operator)
554
-    {
555
-        $query_param_value = $this->getQueryParamValue();
556
-        $model = $this->getContext()->getModel();
557
-        return isset($query_param_value[1])
558
-            && !isset($query_param_value[2])
559
-            && !array_key_exists(
560
-                $operator,
561
-                array_merge(
562
-                    $model->valid_in_style_operators(),
563
-                    $model->valid_null_style_operators(),
564
-                    $model->valid_like_style_operators(),
565
-                    $model->valid_between_style_operators()
566
-                )
567
-            );
568
-    }
569
-
570
-    /**
571
-     * If we're debugging, throws an exception saying that the wrong number of arguments was provided.
572
-     * @since $VID:$
573
-     * @param $operator
574
-     * @throws RestException
575
-     */
576
-    private function throwWrongNumberOfArgsExceptionIfDebugging($operator)
577
-    {
578
-        if (EED_Core_Rest_Api::debugMode()) {
579
-            throw new RestException(
580
-                'wrong_number_of_arguments',
581
-                sprintf(
582
-                    esc_html__(
583
-                        'The operator you provided, "%1$s" had the wrong number of arguments',
584
-                        'event_espresso'
585
-                    ),
586
-                    $operator
587
-                ),
588
-                array(
589
-                    'status' => 400,
590
-                )
591
-            );
592
-        }
593
-    }
594
-
595
-    /**
596
-     * Wrapper for ModelDataTranslator::prepareFieldValuesFromJson(), just a tad more DRY.
597
-     * @since $VID:$
598
-     * @param $value
599
-     * @return mixed
600
-     * @throws RestException
601
-     */
602
-    private function prepareValuesFromJson($value)
603
-    {
604
-        return ModelDataTranslator::prepareFieldValuesFromJson(
605
-            $this->getField(),
606
-            $value,
607
-            $this->getContext()->getRequestedVersion(),
608
-            $this->getTimezone()
609
-        );
610
-    }
611
-
612
-    /**
613
-     * Throws an exception if an invalid operator was specified and we're debugging.
614
-     * @since $VID:$
615
-     * @throws RestException
616
-     */
617
-    private function throwInvalidOperatorExceptionIfDebugging()
618
-    {
619
-        // so they didn't provide a valid operator
620
-        if (EED_Core_Rest_Api::debugMode()) {
621
-            throw new RestException(
622
-                'invalid_operator',
623
-                sprintf(
624
-                    esc_html__(
625
-                        'You provided an invalid parameter, with key "%1$s" and value "%2$s"',
626
-                        'event_espresso'
627
-                    ),
628
-                    $this->getQueryParamKey(),
629
-                    $this->getQueryParamValue()
630
-                ),
631
-                array(
632
-                    'status' => 400,
633
-                )
634
-            );
635
-        }
636
-    }
637
-
638
-    /**
639
-     * Returns true if the query_param_key was a logic query parameter, eg `OR`, `AND`, `NOT`, `OR*`, etc.
640
-     * @since $VID:$
641
-     * @return boolean
642
-     */
643
-    private function isLogicQueryParam()
644
-    {
645
-        return in_array($this->getQueryParamKeySansStars(), $this->getContext()->getModel()->logic_query_param_keys());
646
-    }
647
-
648
-
649
-    /**
650
-     * If the query param isn't for a field, it must be a nested query parameter which requires different logic.
651
-     * @since $VID:$
652
-     * @return array
653
-     * @throws DomainException
654
-     * @throws EE_Error
655
-     * @throws RestException
656
-     * @throws InvalidDataTypeException
657
-     * @throws InvalidInterfaceException
658
-     * @throws InvalidArgumentException
659
-     */
660
-    public function determineNestedConditionQueryParameters()
661
-    {
662
-
663
-        // so this param doesn't correspond to a field eh?
664
-        if ($this->getContext()->isWriting()) {
665
-            // always tell API clients about invalid parameters when they're creating data. Otherwise,
666
-            // they are probably going to create invalid data
667
-            throw new RestException(
668
-                'invalid_field',
669
-                sprintf(
670
-                    /* translators: 1: variable name */
671
-                    esc_html__('You have provided an invalid parameter: "%1$s"', 'event_espresso'),
672
-                    $this->getQueryParamKey()
673
-                )
674
-            );
675
-        }
676
-        // so it's not for a field, is it a logic query param key?
677
-        if ($this->isLogicQueryParam()) {
678
-            return ModelDataTranslator::prepareConditionsQueryParamsForModels(
679
-                $this->getQueryParamValue(),
680
-                $this->getContext()->getModel(),
681
-                $this->getContext()->getRequestedVersion()
682
-            );
683
-        }
684
-        if (EED_Core_Rest_Api::debugMode()) {
685
-            // only tell API clients they got it wrong if we're in debug mode
686
-            // otherwise try our best ot fulfill their request by ignoring this invalid data
687
-            throw new RestException(
688
-                'invalid_parameter',
689
-                sprintf(
690
-                    /* translators: 1: variable name */
691
-                    esc_html__(
692
-                        'You provided an invalid parameter, with key "%1$s"',
693
-                        'event_espresso'
694
-                    ),
695
-                    $this->getQueryParamKey()
696
-                ),
697
-                array(
698
-                    'status' => 400,
699
-                )
700
-            );
701
-        }
702
-        return null;
703
-    }
29
+	private $query_param_key;
30
+	private $query_param_value;
31
+	/**
32
+	 * @var RestIncomingQueryParamContext
33
+	 */
34
+	private $context;
35
+
36
+	/**
37
+	 * @var EE_Model_Field_Base|null
38
+	 */
39
+	private $field;
40
+
41
+	/**
42
+	 * @var string same as $query_param_key but has the * and anything after it removed
43
+	 */
44
+	private $query_param_key_sans_stars;
45
+
46
+	/**
47
+	 * @var string for timezone or timezone offset
48
+	 */
49
+	private $timezone;
50
+
51
+	/**
52
+	 * @var boolean if the field in $query_param_key is for a GMT field (eg `EVT_modified_gmt`)
53
+	 */
54
+	private $is_gmt_field = false;
55
+
56
+	/**
57
+	 * RestIncomingQueryParamMetadata constructor.
58
+	 * You probably want to call
59
+	 * @param string $query_param_key
60
+	 * @param string $query_param_value
61
+	 * @param RestIncomingQueryParamContext $context
62
+	 */
63
+	public function __construct($query_param_key, $query_param_value, RestIncomingQueryParamContext $context)
64
+	{
65
+		$this->query_param_key = $query_param_key;
66
+		$this->query_param_value = $query_param_value;
67
+		$this->context = $context;
68
+		$this->determineFieldAndTimezone();
69
+	}
70
+
71
+	/**
72
+	 * Gets the query parameter key. This may have been modified (see setQueryParamValue())
73
+	 * @return string
74
+	 */
75
+	public function getQueryParamKey()
76
+	{
77
+		return $this->query_param_key;
78
+	}
79
+
80
+	/**
81
+	 * Modifies the query parameter key passed in (Eg this is done when rewriting the simplified specified operator REST
82
+	 * query parameters into the legacy structure)
83
+	 * @param string|array|int|float $query_param_value
84
+	 */
85
+	private function setQueryParamValue($query_param_value)
86
+	{
87
+		$this->query_param_value = $query_param_value;
88
+	}
89
+
90
+	/**
91
+	 * Gets the original query parameter value passed in.
92
+	 * @return string
93
+	 */
94
+	public function getQueryParamValue()
95
+	{
96
+		return $this->query_param_value;
97
+	}
98
+
99
+	/**
100
+	 * Gets the context object.
101
+	 * @return RestIncomingQueryParamContext
102
+	 */
103
+	public function getContext()
104
+	{
105
+		return $this->context;
106
+	}
107
+
108
+	/**
109
+	 * Sets the query parameter key. This may be used to rewrite a key into its non-GMT alternative.
110
+	 * @param string $query_param_key
111
+	 */
112
+	private function setQueryParamKey($query_param_key)
113
+	{
114
+		$this->query_param_key = $query_param_key;
115
+	}
116
+
117
+	/**
118
+	 * Gets the field the query parameter key indicated. This may be null (in cases where the query parameter key
119
+	 * did not indicate a field, eg if it were `OR`).
120
+	 * @return EE_Model_Field_Base|null
121
+	 */
122
+	public function getField()
123
+	{
124
+		return $this->field;
125
+	}
126
+
127
+	/**
128
+	 * Gets the query parameter key (with the star and everything afterwards removed).
129
+	 * @return string
130
+	 */
131
+	public function getQueryParamKeySansStars()
132
+	{
133
+		return $this->query_param_key_sans_stars;
134
+	}
135
+
136
+	/**
137
+	 * Gets the timezone associated with this model (the site timezone, except for GMT datetime fields).
138
+	 * @return string
139
+	 */
140
+	public function getTimezone()
141
+	{
142
+		return $this->timezone;
143
+	}
144
+
145
+	/**
146
+	 * Returns whether or not this is a GMT field
147
+	 * @return boolean
148
+	 */
149
+	public function isGmtField()
150
+	{
151
+		return $this->is_gmt_field;
152
+	}
153
+
154
+	/**
155
+	 * Sets the field indicated by the query parameter key (might be null).
156
+	 * @param EE_Model_Field_Base|null $field
157
+	 */
158
+	private function setField(EE_Model_Field_Base $field = null)
159
+	{
160
+		$this->field = $field;
161
+	}
162
+
163
+	/**
164
+	 * Sets the query parameter key-with-stars-removed.
165
+	 * @param string $query_param_key_sans_stars
166
+	 */
167
+	private function setQueryParamKeySansStars($query_param_key_sans_stars)
168
+	{
169
+		$this->query_param_key_sans_stars = $query_param_key_sans_stars;
170
+	}
171
+
172
+	/**
173
+	 * Sets the timezone (this could be a timezeon offset string).
174
+	 * @param string $timezone
175
+	 */
176
+	private function setTimezone($timezone)
177
+	{
178
+		$this->timezone = $timezone;
179
+	}
180
+
181
+	/**
182
+	 * @param mixed $is_gmt_field
183
+	 */
184
+	private function setIsGmtField($is_gmt_field)
185
+	{
186
+		$this->is_gmt_field = $is_gmt_field;
187
+	}
188
+
189
+	/**
190
+	 * Determines what field, query param name, and query param name without stars, and timezone to use.
191
+	 * @since $VID:$
192
+	 * @type EE_Model_Field_Base $field
193
+	 * @return void {
194
+	 * @throws EE_Error
195
+	 * @throws InvalidDataTypeException
196
+	 * @throws InvalidInterfaceException
197
+	 * @throws InvalidArgumentException
198
+	 */
199
+	private function determineFieldAndTimezone()
200
+	{
201
+		$this->setQueryParamKeySansStars(ModelDataTranslator::removeStarsAndAnythingAfterFromConditionQueryParamKey(
202
+			$this->getQueryParamKey()
203
+		));
204
+		$this->setField(ModelDataTranslator::deduceFieldFromQueryParam(
205
+			$this->getQueryParamKeySansStars(),
206
+			$this->getContext()->getModel()
207
+		));
208
+		// double-check is it a *_gmt field?
209
+		if (!$this->getField() instanceof EE_Model_Field_Base
210
+			&& ModelDataTranslator::isGmtDateFieldName($this->getQueryParamKeySansStars())
211
+		) {
212
+			// yep, take off '_gmt', and find the field
213
+			$this->setQueryParamKey(ModelDataTranslator::removeGmtFromFieldName($this->getQueryParamKeySansStars()));
214
+			$this->setField(ModelDataTranslator::deduceFieldFromQueryParam(
215
+				$this->getQueryParamKey(),
216
+				$this->context->getModel()
217
+			));
218
+			$this->setTimezone('UTC');
219
+			$this->setIsGmtField(true);
220
+		} elseif ($this->getField() instanceof EE_Datetime_Field) {
221
+			// so it's not a GMT field. Set the timezone on the model to the default
222
+			$this->setTimezone(EEH_DTT_Helper::get_valid_timezone_string());
223
+		} else {
224
+			// just keep using what's already set for the timezone
225
+			$this->setTimezone($this->context->getModel()->get_timezone());
226
+		}
227
+	}
228
+
229
+	/**
230
+	 * Given a ton of input, determines the value to use for the models.
231
+	 * @since $VID:$
232
+	 * @return array|null
233
+	 * @throws DomainException
234
+	 * @throws EE_Error
235
+	 * @throws RestException
236
+	 * @throws DomainException
237
+	 */
238
+	public function determineConditionsQueryParameterValue()
239
+	{
240
+		if ($this->valueIsArrayDuringRead()) {
241
+			return $this->determineModelValueGivenRestInputArray();
242
+		}
243
+		return ModelDataTranslator::prepareFieldValueFromJson(
244
+			$this->getField(),
245
+			$this->getQueryParamValue(),
246
+			$this->getContext()->getRequestedVersion(),
247
+			$this->getTimezone()
248
+		);
249
+	}
250
+
251
+	/**
252
+	 * Given that the array value provided was itself an array, handles finding the correct value to pass to the model.
253
+	 * @since $VID:$
254
+	 * @return array|null
255
+	 * @throws RestException
256
+	 */
257
+	private function determineModelValueGivenRestInputArray()
258
+	{
259
+		$this->transformSimplifiedSpecifiedOperatorSyntaxIntoStandardSyntax();
260
+		// did they specify an operator?
261
+		if ($this->valueIsLegacySpecifiedOperator()) {
262
+			$query_param_value = $this->getQueryParamValue();
263
+			$sub_array_key = $query_param_value[0];
264
+			$translated_value = array($sub_array_key);
265
+			if ($this->operatorIsNAry($sub_array_key)) {
266
+				$translated_value[] = $this->prepareValuesFromJson($query_param_value[1]);
267
+			} elseif ($this->operatorIsTernary($sub_array_key)) {
268
+				$translated_value[] = array(
269
+					$this->prepareValuesFromJson($query_param_value[1][0]),
270
+					$this->prepareValuesFromJson($query_param_value[1][1])
271
+				);
272
+			} elseif ($this->operatorIsLike($sub_array_key)) {
273
+				// we want to leave this value mostly-as-is (eg don't force it to be a float
274
+				// or a boolean or an enum value. Leave it as-is with wildcards etc)
275
+				// but do verify it at least doesn't have any serialized data
276
+				ModelDataTranslator::throwExceptionIfContainsSerializedData($query_param_value[1]);
277
+				$translated_value[] = $query_param_value[1];
278
+			} elseif ($this->operatorIsUnary($sub_array_key)) {
279
+				// no arguments should have been provided, so don't look for any
280
+			} elseif ($this->operatorisBinary($sub_array_key)) {
281
+				// it's a valid operator, but none of the exceptions. Treat it normally.
282
+				$translated_value[] = $this->prepareValuesFromJson($query_param_value[1]);
283
+			} else {
284
+				// so they provided a valid operator, but wrong number of arguments
285
+				$this->throwWrongNumberOfArgsExceptionIfDebugging($sub_array_key);
286
+				$translated_value = null;
287
+			}
288
+		} else {
289
+			// so they didn't provide a valid operator
290
+			// if we aren't in debug mode, then just try our best to fulfill the user's request
291
+			$this->throwInvalidOperatorExceptionIfDebugging();
292
+			$translated_value = null;
293
+		}
294
+		return $translated_value;
295
+	}
296
+
297
+	/**
298
+	 * Returns if this request is a "read" request and the value provided was an array.
299
+	 * This will indicate is such things as `array('<', 123)` and `array('IN', array(1,2,3))` are acceptable or not.
300
+	 * @since $VID:$
301
+	 * @return boolean
302
+	 */
303
+	private function valueIsArrayDuringRead()
304
+	{
305
+		return !$this->getContext()->isWriting() && is_array($this->getQueryParamValue());
306
+	}
307
+
308
+	/**
309
+	 * Returns if the value provided was an associative array (we should have already verified it's an array of some
310
+	 * sort). If the value is an associative array, it had better be in the simplified specified operator structure.
311
+	 * @since $VID:$
312
+	 * @return boolean
313
+	 */
314
+	private function valueIsAssociativeArray()
315
+	{
316
+		return !EEH_Array::is_array_numerically_and_sequentially_indexed($this->getQueryParamValue());
317
+	}
318
+
319
+	/**
320
+	 * Checks if the array value is itself an array that fits into the simplified specified operator structure
321
+	 * (eg `array('!=' => 123)`).
322
+	 * @since $VID:$
323
+	 * @return boolean
324
+	 */
325
+	private function valueIsSimplifiedSpecifiedOperator()
326
+	{
327
+		return count($this->getQueryParamValue()) === 1
328
+			&& array_key_exists(
329
+				key($this->getQueryParamValue()),
330
+				$this->getContext()->getModel()->valid_operators()
331
+			);
332
+	}
333
+
334
+	/**
335
+	 * Throws an exception if the sub-value is an array (eg `array('!=' => array())`). It needs to just be a string,
336
+	 * of either comma-separated-values, or a JSON array.
337
+	 * @since $VID:$
338
+	 * @param $sub_array_key
339
+	 * @param $sub_array_value
340
+	 * @throws RestException
341
+	 */
342
+	private function assertSubValueIsntArray($sub_array_key, $sub_array_value)
343
+	{
344
+		if (is_array($sub_array_value) && EED_Core_Rest_Api::debugMode()) {
345
+			throw new RestException(
346
+				'csv_or_json_string_only',
347
+				sprintf(
348
+					/* translators: 1: variable name*/
349
+					esc_html__(
350
+						'The value provided for the operator "%1$s" should be comma-separated value string or a JSON array.',
351
+						'event_espresso'
352
+					),
353
+					$sub_array_key
354
+				),
355
+				array(
356
+					'status' => 400,
357
+				)
358
+			);
359
+		}
360
+	}
361
+
362
+	/**
363
+	 * Determines if the sub-array key is an operator taking 3 or more operators.
364
+	 * @since $VID:$
365
+	 * @param $sub_array_key
366
+	 * @return boolean
367
+	 */
368
+	private function subArrayKeyIsNonBinaryOperator($sub_array_key)
369
+	{
370
+		return array_key_exists(
371
+			$sub_array_key,
372
+			array_merge(
373
+				$this->getContext()->getModel()->valid_in_style_operators(),
374
+				$this->getContext()->getModel()->valid_between_style_operators()
375
+			)
376
+		);
377
+	}
378
+
379
+	/**
380
+	 * Given that the $sub_array_key is a string, checks if it's an operator taking only 1 argument.
381
+	 * @since $VID:$
382
+	 * @param string $sub_array_key
383
+	 * @return boolean
384
+	 */
385
+	private function subArrayKeyIsUnaryOperator($sub_array_key)
386
+	{
387
+		return array_key_exists(
388
+			$sub_array_key,
389
+			$this->getContext()->getModel()->valid_null_style_operators()
390
+		);
391
+	}
392
+
393
+	/**
394
+	 * Parses the $sub_array_value string into an array (given it could either be a comma-separated-list or a JSON
395
+	 * array). eg `"1,2,3"` or `"[1,2,3]"` into `array(1,2,3)`.
396
+	 * @since $VID:$
397
+	 * @param $sub_array_value
398
+	 * @return array|mixed|object
399
+	 */
400
+	private function extractQuickStyleSpecifiedOperatorValue($sub_array_value)
401
+	{
402
+		// the value should be JSON or CSV
403
+		$values = json_decode($sub_array_value);
404
+		if (!is_array($values)) {
405
+			$values = array_filter(
406
+				array_map(
407
+					'trim',
408
+					explode(
409
+						',',
410
+						$sub_array_value
411
+					)
412
+				)
413
+			);
414
+		}
415
+		return $values;
416
+	}
417
+
418
+	/**
419
+	 * Throws an exception if the value isn't a simplified specified operator (only called when we expect that).
420
+	 * @since $VID:$
421
+	 * @throws RestException
422
+	 */
423
+	private function assertSimplifiedSpecifiedOperator()
424
+	{
425
+		if (!$this->valueIsSimplifiedSpecifiedOperator() && EED_Core_Rest_Api::debugMode()) {
426
+			throw new RestException(
427
+				'numerically_indexed_array_of_values_only',
428
+				sprintf(
429
+					/* translators: 1: variable name*/
430
+					esc_html__(
431
+						'The array provided for the parameter "%1$s" should be numerically indexed.',
432
+						'event_espresso'
433
+					),
434
+					$this->getQueryParamKey()
435
+				),
436
+				array(
437
+					'status' => 400,
438
+				)
439
+			);
440
+		}
441
+	}
442
+
443
+	/**
444
+	 * If query_param_value were in the simplified specific operator structure, change it into the legacy structure.
445
+	 * @since $VID:$
446
+	 * @throws RestException
447
+	 */
448
+	private function transformSimplifiedSpecifiedOperatorSyntaxIntoStandardSyntax()
449
+	{
450
+		if ($this->valueIsAssociativeArray()) {
451
+			$this->assertSimplifiedSpecifiedOperator();
452
+			$query_param_value = $this->getQueryParamValue();
453
+			$sub_array_value = reset($query_param_value);
454
+			$sub_array_key = key($query_param_value);
455
+			$this->assertSubValueIsntArray($sub_array_key, $sub_array_value);
456
+			// they're doing something like "&where[EVT_ID][IN]=1,2,3" or "&where[EVT_ID][>]=5"
457
+			if ($this->subArrayKeyIsNonBinaryOperator($sub_array_key)) {
458
+				$this->setQueryParamValue(array(
459
+					$sub_array_key,
460
+					$this->extractQuickStyleSpecifiedOperatorValue($sub_array_value)
461
+				));
462
+			} elseif ($this->subArrayKeyIsUnaryOperator($sub_array_key)) {
463
+				$this->setQueryParamValue(array($sub_array_key));
464
+			} else {
465
+				$this->setQueryParamValue(array($sub_array_key, $sub_array_value));
466
+			}
467
+		}
468
+	}
469
+
470
+	/**
471
+	 * Returns true is the value is an array using the legacy structure to specify the operator. Eg `array('!=',123)`.
472
+	 * @since $VID:$
473
+	 * @return boolean
474
+	 */
475
+	private function valueIsLegacySpecifiedOperator()
476
+	{
477
+		$valid_operators = $this->getContext()->getModel()->valid_operators();
478
+		$query_param_value = $this->getQueryParamValue();
479
+		return isset($query_param_value[0])
480
+			&& isset($valid_operators[ $query_param_value[0] ]);
481
+	}
482
+
483
+	/**
484
+	 * Returns true if the value specified operator accepts arbitrary number of arguments, like "IN".
485
+	 * @since $VID:$
486
+	 * @param $operator
487
+	 * @return boolean
488
+	 */
489
+	private function operatorIsNAry($operator)
490
+	{
491
+		$valueArray = $this->getQueryParamValue();
492
+		return array_key_exists(
493
+			$operator,
494
+			$this->getContext()->getModel()->valid_in_style_operators()
495
+		)
496
+			&& isset($valueArray[1])
497
+			&& is_array($valueArray[1])
498
+			&& !isset($valueArray[2]);
499
+	}
500
+
501
+	/**
502
+	 * Returns true if the operator accepts 3 arguments (eg "BETWEEN").
503
+	 * So we're looking for a value that looks like
504
+	 * `array('BETWEEN', array('2015-01-01T00:00:00', '2016-01-01T00:00:00'))`.
505
+	 * @since $VID:$
506
+	 * @param $operator
507
+	 * @return boolean
508
+	 */
509
+	private function operatorIsTernary($operator)
510
+	{
511
+		$query_param_value = $this->getQueryParamValue();
512
+		return array_key_exists($operator, $this->getContext()->getModel()->valid_between_style_operators())
513
+			&& isset($query_param_value[1])
514
+			&& is_array($query_param_value[1])
515
+			&& isset($query_param_value[1][0], $query_param_value[1][1])
516
+			&& !isset($query_param_value[1][2])
517
+			&& !isset($query_param_value[2]);
518
+	}
519
+
520
+	/**
521
+	 * Returns true if the operator is a similar to LIKE, indicating the value may have wildcards we should leave alone.
522
+	 * @since $VID:$
523
+	 * @param $operator
524
+	 * @return boolean
525
+	 */
526
+	private function operatorIsLike($operator)
527
+	{
528
+		$query_param_value = $this->getQueryParamValue();
529
+		return array_key_exists($operator, $this->getContext()->getModel()->valid_like_style_operators())
530
+			&& isset($query_param_value[1])
531
+			&& !isset($query_param_value[2]);
532
+	}
533
+
534
+	/**
535
+	 * Returns true if the operator only takes one argument (eg it's like `IS NULL`).
536
+	 * @since $VID:$
537
+	 * @param $operator
538
+	 * @return boolean
539
+	 */
540
+	private function operatorIsUnary($operator)
541
+	{
542
+		$query_param_value = $this->getQueryParamValue();
543
+		return array_key_exists($operator, $this->getContext()->getModel()->valid_null_style_operators())
544
+			&& !isset($query_param_value[1]);
545
+	}
546
+
547
+	/**
548
+	 * Returns true if the operator specified is a binary opeator (eg `=`, `!=`)
549
+	 * @since $VID:$
550
+	 * @param $operator
551
+	 * @return boolean
552
+	 */
553
+	private function operatorisBinary($operator)
554
+	{
555
+		$query_param_value = $this->getQueryParamValue();
556
+		$model = $this->getContext()->getModel();
557
+		return isset($query_param_value[1])
558
+			&& !isset($query_param_value[2])
559
+			&& !array_key_exists(
560
+				$operator,
561
+				array_merge(
562
+					$model->valid_in_style_operators(),
563
+					$model->valid_null_style_operators(),
564
+					$model->valid_like_style_operators(),
565
+					$model->valid_between_style_operators()
566
+				)
567
+			);
568
+	}
569
+
570
+	/**
571
+	 * If we're debugging, throws an exception saying that the wrong number of arguments was provided.
572
+	 * @since $VID:$
573
+	 * @param $operator
574
+	 * @throws RestException
575
+	 */
576
+	private function throwWrongNumberOfArgsExceptionIfDebugging($operator)
577
+	{
578
+		if (EED_Core_Rest_Api::debugMode()) {
579
+			throw new RestException(
580
+				'wrong_number_of_arguments',
581
+				sprintf(
582
+					esc_html__(
583
+						'The operator you provided, "%1$s" had the wrong number of arguments',
584
+						'event_espresso'
585
+					),
586
+					$operator
587
+				),
588
+				array(
589
+					'status' => 400,
590
+				)
591
+			);
592
+		}
593
+	}
594
+
595
+	/**
596
+	 * Wrapper for ModelDataTranslator::prepareFieldValuesFromJson(), just a tad more DRY.
597
+	 * @since $VID:$
598
+	 * @param $value
599
+	 * @return mixed
600
+	 * @throws RestException
601
+	 */
602
+	private function prepareValuesFromJson($value)
603
+	{
604
+		return ModelDataTranslator::prepareFieldValuesFromJson(
605
+			$this->getField(),
606
+			$value,
607
+			$this->getContext()->getRequestedVersion(),
608
+			$this->getTimezone()
609
+		);
610
+	}
611
+
612
+	/**
613
+	 * Throws an exception if an invalid operator was specified and we're debugging.
614
+	 * @since $VID:$
615
+	 * @throws RestException
616
+	 */
617
+	private function throwInvalidOperatorExceptionIfDebugging()
618
+	{
619
+		// so they didn't provide a valid operator
620
+		if (EED_Core_Rest_Api::debugMode()) {
621
+			throw new RestException(
622
+				'invalid_operator',
623
+				sprintf(
624
+					esc_html__(
625
+						'You provided an invalid parameter, with key "%1$s" and value "%2$s"',
626
+						'event_espresso'
627
+					),
628
+					$this->getQueryParamKey(),
629
+					$this->getQueryParamValue()
630
+				),
631
+				array(
632
+					'status' => 400,
633
+				)
634
+			);
635
+		}
636
+	}
637
+
638
+	/**
639
+	 * Returns true if the query_param_key was a logic query parameter, eg `OR`, `AND`, `NOT`, `OR*`, etc.
640
+	 * @since $VID:$
641
+	 * @return boolean
642
+	 */
643
+	private function isLogicQueryParam()
644
+	{
645
+		return in_array($this->getQueryParamKeySansStars(), $this->getContext()->getModel()->logic_query_param_keys());
646
+	}
647
+
648
+
649
+	/**
650
+	 * If the query param isn't for a field, it must be a nested query parameter which requires different logic.
651
+	 * @since $VID:$
652
+	 * @return array
653
+	 * @throws DomainException
654
+	 * @throws EE_Error
655
+	 * @throws RestException
656
+	 * @throws InvalidDataTypeException
657
+	 * @throws InvalidInterfaceException
658
+	 * @throws InvalidArgumentException
659
+	 */
660
+	public function determineNestedConditionQueryParameters()
661
+	{
662
+
663
+		// so this param doesn't correspond to a field eh?
664
+		if ($this->getContext()->isWriting()) {
665
+			// always tell API clients about invalid parameters when they're creating data. Otherwise,
666
+			// they are probably going to create invalid data
667
+			throw new RestException(
668
+				'invalid_field',
669
+				sprintf(
670
+					/* translators: 1: variable name */
671
+					esc_html__('You have provided an invalid parameter: "%1$s"', 'event_espresso'),
672
+					$this->getQueryParamKey()
673
+				)
674
+			);
675
+		}
676
+		// so it's not for a field, is it a logic query param key?
677
+		if ($this->isLogicQueryParam()) {
678
+			return ModelDataTranslator::prepareConditionsQueryParamsForModels(
679
+				$this->getQueryParamValue(),
680
+				$this->getContext()->getModel(),
681
+				$this->getContext()->getRequestedVersion()
682
+			);
683
+		}
684
+		if (EED_Core_Rest_Api::debugMode()) {
685
+			// only tell API clients they got it wrong if we're in debug mode
686
+			// otherwise try our best ot fulfill their request by ignoring this invalid data
687
+			throw new RestException(
688
+				'invalid_parameter',
689
+				sprintf(
690
+					/* translators: 1: variable name */
691
+					esc_html__(
692
+						'You provided an invalid parameter, with key "%1$s"',
693
+						'event_espresso'
694
+					),
695
+					$this->getQueryParamKey()
696
+				),
697
+				array(
698
+					'status' => 400,
699
+				)
700
+			);
701
+		}
702
+		return null;
703
+	}
704 704
 }
705 705
 // End of file RestQueryParamMetadata.php
706 706
 // Location: EventEspresso\core\libraries\rest_api/RestQueryParamMetadata.php
Please login to merge, or discard this patch.