Completed
Branch FET/download-plugin-prompt (0ab3a7)
by
unknown
02:44 queued 14s
created
core/libraries/rest_api/RestIncomingQueryParamMetadata.php 2 patches
Spacing   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -489,7 +489,7 @@  discard block
 block discarded – undo
489 489
     {
490 490
         // the value should be JSON or CSV
491 491
         $values = json_decode($sub_array_value);
492
-        if (! is_array($values)) {
492
+        if ( ! is_array($values)) {
493 493
             $values = array_filter(
494 494
                 array_map(
495 495
                     'trim',
@@ -512,7 +512,7 @@  discard block
 block discarded – undo
512 512
      */
513 513
     private function assertSimplifiedSpecifiedOperator()
514 514
     {
515
-        if (! $this->valueIsSimplifiedSpecifiedOperator() && EED_Core_Rest_Api::debugMode()) {
515
+        if ( ! $this->valueIsSimplifiedSpecifiedOperator() && EED_Core_Rest_Api::debugMode()) {
516 516
             throw new RestException(
517 517
                 'numerically_indexed_array_of_values_only',
518 518
                 sprintf(
@@ -573,7 +573,7 @@  discard block
 block discarded – undo
573 573
         $valid_operators   = $this->getContext()->getModel()->valid_operators();
574 574
         $query_param_value = $this->getQueryParamValue();
575 575
         return isset($query_param_value[0])
576
-               && isset($valid_operators[ $query_param_value[0] ]);
576
+               && isset($valid_operators[$query_param_value[0]]);
577 577
     }
578 578
 
579 579
 
Please login to merge, or discard this patch.
Indentation   +795 added lines, -795 removed lines patch added patch discarded remove patch
@@ -29,801 +29,801 @@
 block discarded – undo
29 29
  */
30 30
 class RestIncomingQueryParamMetadata
31 31
 {
32
-    /**
33
-     * @var string
34
-     */
35
-    private $query_param_key;
36
-
37
-    /**
38
-     * @var mixed
39
-     */
40
-    private $query_param_value;
41
-
42
-    /**
43
-     * @var RestIncomingQueryParamContext
44
-     */
45
-    private $context;
46
-
47
-    /**
48
-     * @var EE_Model_Field_Base|null
49
-     */
50
-    private $field;
51
-
52
-    /**
53
-     * @var string same as $query_param_key but has the * and anything after it removed
54
-     */
55
-    private $query_param_key_sans_stars;
56
-
57
-    /**
58
-     * @var string for timezone or timezone offset
59
-     */
60
-    private $timezone;
61
-
62
-    /**
63
-     * @var boolean if the field in $query_param_key is for a GMT field (eg `EVT_modified_gmt`)
64
-     */
65
-    private $is_gmt_field = false;
66
-
67
-
68
-    /**
69
-     * RestIncomingQueryParamMetadata constructor.
70
-     * You probably want to call
71
-     *
72
-     * @param string                        $query_param_key
73
-     * @param mixed                         $query_param_value
74
-     * @param RestIncomingQueryParamContext $context
75
-     * @throws EE_Error
76
-     * @throws RestException
77
-     * @throws ReflectionException
78
-     */
79
-    public function __construct(string $query_param_key, $query_param_value, RestIncomingQueryParamContext $context)
80
-    {
81
-        $this->query_param_key   = $query_param_key;
82
-        $this->query_param_value = $query_param_value;
83
-        $this->context           = $context;
84
-        $this->determineFieldAndTimezone();
85
-    }
86
-
87
-
88
-    /**
89
-     * Gets the query parameter key. This may have been modified (see setQueryParamValue())
90
-     *
91
-     * @return string
92
-     */
93
-    public function getQueryParamKey(): string
94
-    {
95
-        return $this->query_param_key;
96
-    }
97
-
98
-
99
-    /**
100
-     * Modifies the query parameter key passed in (Eg this is done when rewriting the simplified specified operator REST
101
-     * query parameters into the legacy structure)
102
-     *
103
-     * @param string|array|int|float $query_param_value
104
-     */
105
-    private function setQueryParamValue($query_param_value)
106
-    {
107
-        $this->query_param_value = $query_param_value;
108
-    }
109
-
110
-
111
-    /**
112
-     * Gets the original query parameter value passed in.
113
-     *
114
-     * @return mixed
115
-     */
116
-    public function getQueryParamValue()
117
-    {
118
-        return $this->query_param_value;
119
-    }
120
-
121
-
122
-    /**
123
-     * Gets the context object.
124
-     *
125
-     * @return RestIncomingQueryParamContext
126
-     */
127
-    public function getContext(): RestIncomingQueryParamContext
128
-    {
129
-        return $this->context;
130
-    }
131
-
132
-
133
-    /**
134
-     * Sets the query parameter key. This may be used to rewrite a key into its non-GMT alternative.
135
-     *
136
-     * @param string $query_param_key
137
-     */
138
-    private function setQueryParamKey(string $query_param_key)
139
-    {
140
-        $this->query_param_key = $query_param_key;
141
-    }
142
-
143
-
144
-    /**
145
-     * Gets the field the query parameter key indicated. This may be null (in cases where the query parameter key
146
-     * did not indicate a field, eg if it were `OR`).
147
-     *
148
-     * @return EE_Model_Field_Base|null
149
-     */
150
-    public function getField(): ?EE_Model_Field_Base
151
-    {
152
-        return $this->field;
153
-    }
154
-
155
-
156
-    /**
157
-     * Gets the query parameter key (with the star and everything afterwards removed).
158
-     *
159
-     * @return string
160
-     */
161
-    public function getQueryParamKeySansStars(): string
162
-    {
163
-        return $this->query_param_key_sans_stars;
164
-    }
165
-
166
-
167
-    /**
168
-     * Gets the timezone associated with this model (the site timezone, except for GMT datetime fields).
169
-     *
170
-     * @return string
171
-     */
172
-    public function getTimezone(): string
173
-    {
174
-        return $this->timezone;
175
-    }
176
-
177
-
178
-    /**
179
-     * Returns whether or not this is a GMT field
180
-     *
181
-     * @return boolean
182
-     */
183
-    public function isGmtField(): bool
184
-    {
185
-        return $this->is_gmt_field;
186
-    }
187
-
188
-
189
-    /**
190
-     * Sets the field indicated by the query parameter key (might be null).
191
-     *
192
-     * @param EE_Model_Field_Base|null $field
193
-     */
194
-    private function setField(EE_Model_Field_Base $field = null)
195
-    {
196
-        $this->field = $field;
197
-    }
198
-
199
-
200
-    /**
201
-     * Sets the query parameter key-with-stars-removed.
202
-     *
203
-     * @param string $query_param_key_sans_stars
204
-     */
205
-    private function setQueryParamKeySansStars(string $query_param_key_sans_stars)
206
-    {
207
-        $this->query_param_key_sans_stars = $query_param_key_sans_stars;
208
-    }
209
-
210
-
211
-    /**
212
-     * Sets the timezone (this could be a timezone offset string).
213
-     *
214
-     * @param string $timezone
215
-     */
216
-    private function setTimezone(string $timezone)
217
-    {
218
-        $this->timezone = $timezone;
219
-    }
220
-
221
-
222
-    /**
223
-     * @param bool|int|string $is_gmt_field
224
-     */
225
-    private function setIsGmtField($is_gmt_field)
226
-    {
227
-        $this->is_gmt_field = filter_var($is_gmt_field, FILTER_VALIDATE_BOOLEAN);
228
-    }
229
-
230
-
231
-    /**
232
-     * Determines what field, query param name, and query param name without stars, and timezone to use.
233
-     *
234
-     * @return void {
235
-     * @throws EE_Error
236
-     * @throws RestException
237
-     * @throws ReflectionException
238
-     * @since 4.9.72.p
239
-     *
240
-     */
241
-    private function determineFieldAndTimezone()
242
-    {
243
-        $this->setQueryParamKeySansStars(
244
-            ModelDataTranslator::removeStarsAndAnythingAfterFromConditionQueryParamKey(
245
-                $this->getQueryParamKey()
246
-            )
247
-        );
248
-        $this->setField(
249
-            ModelDataTranslator::deduceFieldFromQueryParam(
250
-                $this->getQueryParamKeySansStars(),
251
-                $this->getContext()->getModel()
252
-            )
253
-        );
254
-        // double-check is it a *_gmt field?
255
-        if (
256
-            ! $this->getField() instanceof EE_Model_Field_Base
257
-            && ModelDataTranslator::isGmtDateFieldName($this->getQueryParamKeySansStars())
258
-        ) {
259
-            // yep, take off '_gmt', and find the field
260
-            $this->setQueryParamKey(ModelDataTranslator::removeGmtFromFieldName($this->getQueryParamKeySansStars()));
261
-            $this->setField(
262
-                ModelDataTranslator::deduceFieldFromQueryParam(
263
-                    $this->getQueryParamKey(),
264
-                    $this->context->getModel()
265
-                )
266
-            );
267
-            $this->setTimezone('UTC');
268
-            $this->setIsGmtField(true);
269
-        } elseif ($this->getField() instanceof EE_Datetime_Field) {
270
-            // so it's not a GMT field. Set the timezone on the model to the default
271
-            $this->setTimezone(EEH_DTT_Helper::get_valid_timezone_string());
272
-        } else {
273
-            // just keep using what's already set for the timezone
274
-            $this->setTimezone($this->context->getModel()->get_timezone());
275
-        }
276
-        $this->assertOnlyAdminCanReadPasswordFields();
277
-    }
278
-
279
-
280
-    /**
281
-     * Throws an exception if a non-admin is trying to query by password.
282
-     *
283
-     * @throws RestException
284
-     * @since 4.9.74.p
285
-     */
286
-    private function assertOnlyAdminCanReadPasswordFields()
287
-    {
288
-        if (
289
-            $this->getField() instanceof EE_Password_Field
290
-            && ! current_user_can(EE_Restriction_Generator_Base::get_default_restrictions_cap())
291
-        ) {
292
-            // only full admins can query by password. sorry bub!
293
-            throw new RestException(
294
-                'only_admins_can_query_by_password',
295
-                // @codingStandardsIgnoreStart
296
-                esc_html__(
297
-                    'You attempted to filter by a password field without the needed privileges. Only a full admin is allowed to do that.',
298
-                    'event_espresso'
299
-                ),
300
-                // @codingStandardsIgnoreEnd
301
-                [
302
-                    'status' => 403,
303
-                ]
304
-            );
305
-        }
306
-    }
307
-
308
-
309
-    /**
310
-     * Given a ton of input, determines the value to use for the models.
311
-     *
312
-     * @return array|null
313
-     * @throws DomainException
314
-     * @throws EE_Error
315
-     * @throws RestException
316
-     * @throws DomainException
317
-     * @since 4.9.72.p
318
-     */
319
-    public function determineConditionsQueryParameterValue(): ?array
320
-    {
321
-        if ($this->valueIsArrayDuringRead()) {
322
-            return $this->determineModelValueGivenRestInputArray();
323
-        }
324
-        return ModelDataTranslator::prepareFieldValueFromJson(
325
-            $this->getField(),
326
-            $this->getQueryParamValue(),
327
-            $this->getContext()->getRequestedVersion(),
328
-            $this->getTimezone()
329
-        );
330
-    }
331
-
332
-
333
-    /**
334
-     * Given that the array value provided was itself an array, handles finding the correct value to pass to the model.
335
-     *
336
-     * @return array|null
337
-     * @throws EE_Error
338
-     * @throws RestException
339
-     * @since 4.9.72.p
340
-     */
341
-    private function determineModelValueGivenRestInputArray(): ?array
342
-    {
343
-        $this->transformSimplifiedSpecifiedOperatorSyntaxIntoStandardSyntax();
344
-        // did they specify an operator?
345
-        if ($this->valueIsLegacySpecifiedOperator()) {
346
-            $query_param_value = $this->getQueryParamValue();
347
-            $sub_array_key     = $query_param_value[0];
348
-            $translated_value  = [$sub_array_key];
349
-            if ($this->operatorIsNAry($sub_array_key)) {
350
-                $translated_value[] = $this->prepareValuesFromJson($query_param_value[1]);
351
-            } elseif ($this->operatorIsTernary($sub_array_key)) {
352
-                $translated_value[] = [
353
-                    $this->prepareValuesFromJson($query_param_value[1][0]),
354
-                    $this->prepareValuesFromJson($query_param_value[1][1]),
355
-                ];
356
-            } elseif ($this->operatorIsLike($sub_array_key)) {
357
-                // we want to leave this value mostly-as-is (eg don't force it to be a float
358
-                // or a boolean or an enum value. Leave it as-is with wildcards etc)
359
-                // but do verify it at least doesn't have any serialized data
360
-                ModelDataTranslator::throwExceptionIfContainsSerializedData($query_param_value[1]);
361
-                $translated_value[] = $query_param_value[1];
362
-            } elseif ($this->operatorIsUnary($sub_array_key)) {
363
-                // no arguments should have been provided, so don't look for any
364
-            } elseif ($this->operatorIsBinary($sub_array_key)) {
365
-                // it's a valid operator, but none of the exceptions. Treat it normally.
366
-                $translated_value[] = $this->prepareValuesFromJson($query_param_value[1]);
367
-            } else {
368
-                // so they provided a valid operator, but wrong number of arguments
369
-                $this->throwWrongNumberOfArgsExceptionIfDebugging($sub_array_key);
370
-                $translated_value = null;
371
-            }
372
-        } else {
373
-            // so they didn't provide a valid operator
374
-            // if we aren't in debug mode, then just try our best to fulfill the user's request
375
-            $this->throwInvalidOperatorExceptionIfDebugging();
376
-            $translated_value = null;
377
-        }
378
-        return $translated_value;
379
-    }
380
-
381
-
382
-    /**
383
-     * Returns if this request is a "read" request and the value provided was an array.
384
-     * This will indicate is such things as `array('<', 123)` and `array('IN', array(1,2,3))` are acceptable or not.
385
-     *
386
-     * @return boolean
387
-     * @since 4.9.72.p
388
-     */
389
-    private function valueIsArrayDuringRead(): bool
390
-    {
391
-        return ! $this->getContext()->isWriting() && is_array($this->getQueryParamValue());
392
-    }
393
-
394
-
395
-    /**
396
-     * Returns if the value provided was an associative array (we should have already verified it's an array of some
397
-     * sort). If the value is an associative array, it had better be in the simplified specified operator structure.
398
-     *
399
-     * @return boolean
400
-     * @since 4.9.72.p
401
-     */
402
-    private function valueIsAssociativeArray(): bool
403
-    {
404
-        return ! EEH_Array::is_array_numerically_and_sequentially_indexed((array) $this->getQueryParamValue());
405
-    }
406
-
407
-
408
-    /**
409
-     * Checks if the array value is itself an array that fits into the simplified specified operator structure
410
-     * (eg `array('!=' => 123)`).
411
-     *
412
-     * @return boolean
413
-     * @since 4.9.72.p
414
-     */
415
-    private function valueIsSimplifiedSpecifiedOperator(): bool
416
-    {
417
-        return count($this->getQueryParamValue()) === 1
418
-               && array_key_exists(
419
-                   key($this->getQueryParamValue()),
420
-                   $this->getContext()->getModel()->valid_operators()
421
-               );
422
-    }
423
-
424
-
425
-    /**
426
-     * Throws an exception if the sub-value is an array (eg `array('!=' => array())`). It needs to just be a string,
427
-     * of either comma-separated-values, or a JSON array.
428
-     *
429
-     * @param $sub_array_key
430
-     * @param $sub_array_value
431
-     * @throws RestException
432
-     * @since 4.9.72.p
433
-     */
434
-    private function assertSubValueIsNotArray($sub_array_key, $sub_array_value)
435
-    {
436
-        if (is_array($sub_array_value) && EED_Core_Rest_Api::debugMode()) {
437
-            throw new RestException(
438
-                'csv_or_json_string_only',
439
-                sprintf(
440
-                /* translators: 1: variable name*/
441
-                    esc_html__(
442
-                        'The value provided for the operator "%1$s" should be comma-separated value string or a JSON array.',
443
-                        'event_espresso'
444
-                    ),
445
-                    $sub_array_key
446
-                ),
447
-                [
448
-                    'status' => 400,
449
-                ]
450
-            );
451
-        }
452
-    }
453
-
454
-
455
-    /**
456
-     * Determines if the sub-array key is an operator taking 3 or more operators.
457
-     *
458
-     * @param $sub_array_key
459
-     * @return boolean
460
-     * @since 4.9.72.p
461
-     */
462
-    private function subArrayKeyIsNonBinaryOperator($sub_array_key): bool
463
-    {
464
-        return array_key_exists(
465
-            $sub_array_key,
466
-            array_merge(
467
-                $this->getContext()->getModel()->valid_in_style_operators(),
468
-                $this->getContext()->getModel()->valid_between_style_operators()
469
-            )
470
-        );
471
-    }
472
-
473
-
474
-    /**
475
-     * Given that the $sub_array_key is a string, checks if it's an operator taking only 1 argument.
476
-     *
477
-     * @param string $sub_array_key
478
-     * @return boolean
479
-     * @since 4.9.72.p
480
-     */
481
-    private function subArrayKeyIsUnaryOperator(string $sub_array_key): bool
482
-    {
483
-        return array_key_exists(
484
-            $sub_array_key,
485
-            $this->getContext()->getModel()->valid_null_style_operators()
486
-        );
487
-    }
488
-
489
-
490
-    /**
491
-     * Parses the $sub_array_value string into an array (given it could either be a comma-separated-list or a JSON
492
-     * array). eg `"1,2,3"` or `"[1,2,3]"` into `array(1,2,3)`.
493
-     *
494
-     * @param $sub_array_value
495
-     * @return array|mixed|object
496
-     * @since 4.9.72.p
497
-     */
498
-    private function extractQuickStyleSpecifiedOperatorValue($sub_array_value)
499
-    {
500
-        // the value should be JSON or CSV
501
-        $values = json_decode($sub_array_value);
502
-        if (! is_array($values)) {
503
-            $values = array_filter(
504
-                array_map(
505
-                    'trim',
506
-                    explode(
507
-                        ',',
508
-                        $sub_array_value
509
-                    )
510
-                )
511
-            );
512
-        }
513
-        return $values;
514
-    }
515
-
516
-
517
-    /**
518
-     * Throws an exception if the value isn't a simplified specified operator (only called when we expect that).
519
-     *
520
-     * @throws RestException
521
-     * @since 4.9.72.p
522
-     */
523
-    private function assertSimplifiedSpecifiedOperator()
524
-    {
525
-        if (! $this->valueIsSimplifiedSpecifiedOperator() && EED_Core_Rest_Api::debugMode()) {
526
-            throw new RestException(
527
-                'numerically_indexed_array_of_values_only',
528
-                sprintf(
529
-                /* translators: 1: variable name*/
530
-                    esc_html__(
531
-                        'The array provided for the parameter "%1$s" should be numerically indexed.',
532
-                        'event_espresso'
533
-                    ),
534
-                    $this->getQueryParamKey()
535
-                ),
536
-                [
537
-                    'status' => 400,
538
-                ]
539
-            );
540
-        }
541
-    }
542
-
543
-
544
-    /**
545
-     * If query_param_value were in the simplified specific operator structure, change it into the legacy structure.
546
-     *
547
-     * @throws RestException
548
-     * @since 4.9.72.p
549
-     */
550
-    private function transformSimplifiedSpecifiedOperatorSyntaxIntoStandardSyntax()
551
-    {
552
-        if ($this->valueIsAssociativeArray()) {
553
-            $this->assertSimplifiedSpecifiedOperator();
554
-            $query_param_value = $this->getQueryParamValue();
555
-            $sub_array_value   = reset($query_param_value);
556
-            $sub_array_key     = key($query_param_value);
557
-            $this->assertSubValueIsNotArray($sub_array_key, $sub_array_value);
558
-            // they're doing something like "&where[EVT_ID][IN]=1,2,3" or "&where[EVT_ID][>]=5"
559
-            if ($this->subArrayKeyIsNonBinaryOperator($sub_array_key)) {
560
-                $this->setQueryParamValue([
561
-                                              $sub_array_key,
562
-                                              $this->extractQuickStyleSpecifiedOperatorValue($sub_array_value),
563
-                                          ]);
564
-            } elseif ($this->subArrayKeyIsUnaryOperator($sub_array_key)) {
565
-                $this->setQueryParamValue([$sub_array_key]);
566
-            } else {
567
-                $this->setQueryParamValue([$sub_array_key, $sub_array_value]);
568
-            }
569
-        }
570
-    }
571
-
572
-
573
-    /**
574
-     * Returns true is the value is an array using the legacy structure to specify the operator. Eg `array('!=',123)`.
575
-     *
576
-     * @return boolean
577
-     * @since 4.9.72.p
578
-     */
579
-    private function valueIsLegacySpecifiedOperator(): bool
580
-    {
581
-        $valid_operators   = $this->getContext()->getModel()->valid_operators();
582
-        $query_param_value = $this->getQueryParamValue();
583
-        return isset($query_param_value[0])
584
-               && isset($valid_operators[ $query_param_value[0] ]);
585
-    }
586
-
587
-
588
-    /**
589
-     * Returns true if the value specified operator accepts arbitrary number of arguments, like "IN".
590
-     *
591
-     * @param $operator
592
-     * @return boolean
593
-     * @since 4.9.72.p
594
-     */
595
-    private function operatorIsNAry($operator): bool
596
-    {
597
-        $valueArray = $this->getQueryParamValue();
598
-        return array_key_exists(
599
-                   $operator,
600
-                   $this->getContext()->getModel()->valid_in_style_operators()
601
-               )
602
-               && isset($valueArray[1])
603
-               && is_array($valueArray[1])
604
-               && ! isset($valueArray[2]);
605
-    }
606
-
607
-
608
-    /**
609
-     * Returns true if the operator accepts 3 arguments (eg "BETWEEN").
610
-     * So we're looking for a value that looks like
611
-     * `array('BETWEEN', array('2015-01-01T00:00:00', '2016-01-01T00:00:00'))`.
612
-     *
613
-     * @param $operator
614
-     * @return boolean
615
-     * @since 4.9.72.p
616
-     */
617
-    private function operatorIsTernary($operator): bool
618
-    {
619
-        $query_param_value = $this->getQueryParamValue();
620
-        return array_key_exists($operator, $this->getContext()->getModel()->valid_between_style_operators())
621
-               && isset($query_param_value[1])
622
-               && is_array($query_param_value[1])
623
-               && isset($query_param_value[1][0], $query_param_value[1][1])
624
-               && ! isset($query_param_value[1][2])
625
-               && ! isset($query_param_value[2]);
626
-    }
627
-
628
-
629
-    /**
630
-     * Returns true if the operator is a similar to LIKE, indicating the value may have wildcards we should leave alone.
631
-     *
632
-     * @param $operator
633
-     * @return boolean
634
-     * @since 4.9.72.p
635
-     */
636
-    private function operatorIsLike($operator): bool
637
-    {
638
-        $query_param_value = $this->getQueryParamValue();
639
-        return array_key_exists($operator, $this->getContext()->getModel()->valid_like_style_operators())
640
-               && isset($query_param_value[1])
641
-               && ! isset($query_param_value[2]);
642
-    }
643
-
644
-
645
-    /**
646
-     * Returns true if the operator only takes one argument (eg it's like `IS NULL`).
647
-     *
648
-     * @param $operator
649
-     * @return boolean
650
-     * @since 4.9.72.p
651
-     */
652
-    private function operatorIsUnary($operator): bool
653
-    {
654
-        $query_param_value = $this->getQueryParamValue();
655
-        return array_key_exists($operator, $this->getContext()->getModel()->valid_null_style_operators())
656
-               && ! isset($query_param_value[1]);
657
-    }
658
-
659
-
660
-    /**
661
-     * Returns true if the operator specified is a binary operator (eg `=`, `!=`)
662
-     *
663
-     * @param $operator
664
-     * @return boolean
665
-     * @since 4.9.72.p
666
-     */
667
-    private function operatorIsBinary($operator): bool
668
-    {
669
-        $query_param_value = $this->getQueryParamValue();
670
-        $model             = $this->getContext()->getModel();
671
-        return isset($query_param_value[1])
672
-               && ! isset($query_param_value[2])
673
-               && ! array_key_exists(
674
-                $operator,
675
-                array_merge(
676
-                    $model->valid_in_style_operators(),
677
-                    $model->valid_null_style_operators(),
678
-                    $model->valid_like_style_operators(),
679
-                    $model->valid_between_style_operators()
680
-                )
681
-            );
682
-    }
683
-
684
-
685
-    /**
686
-     * If we're debugging, throws an exception saying that the wrong number of arguments was provided.
687
-     *
688
-     * @param $operator
689
-     * @throws RestException
690
-     * @since 4.9.72.p
691
-     */
692
-    private function throwWrongNumberOfArgsExceptionIfDebugging($operator)
693
-    {
694
-        if (EED_Core_Rest_Api::debugMode()) {
695
-            throw new RestException(
696
-                'wrong_number_of_arguments',
697
-                sprintf(
698
-                    esc_html__(
699
-                        'The operator you provided, "%1$s" had the wrong number of arguments',
700
-                        'event_espresso'
701
-                    ),
702
-                    $operator
703
-                ),
704
-                [
705
-                    'status' => 400,
706
-                ]
707
-            );
708
-        }
709
-    }
710
-
711
-
712
-    /**
713
-     * Wrapper for ModelDataTranslator::prepareFieldValuesFromJson(), just a tad more DRY.
714
-     *
715
-     * @param $value
716
-     * @return mixed
717
-     * @throws EE_Error
718
-     * @throws RestException
719
-     * @since 4.9.72.p
720
-     */
721
-    private function prepareValuesFromJson($value)
722
-    {
723
-        return ModelDataTranslator::prepareFieldValuesFromJson(
724
-            $this->getField(),
725
-            $value,
726
-            $this->getContext()->getRequestedVersion(),
727
-            $this->getTimezone()
728
-        );
729
-    }
730
-
731
-
732
-    /**
733
-     * Throws an exception if an invalid operator was specified and we're debugging.
734
-     *
735
-     * @throws RestException
736
-     * @since 4.9.72.p
737
-     */
738
-    private function throwInvalidOperatorExceptionIfDebugging()
739
-    {
740
-        // so they didn't provide a valid operator
741
-        if (EED_Core_Rest_Api::debugMode()) {
742
-            throw new RestException(
743
-                'invalid_operator',
744
-                sprintf(
745
-                    esc_html__(
746
-                        'You provided an invalid parameter, with key "%1$s" and value "%2$s"',
747
-                        'event_espresso'
748
-                    ),
749
-                    $this->getQueryParamKey(),
750
-                    $this->getQueryParamValue()
751
-                ),
752
-                [
753
-                    'status' => 400,
754
-                ]
755
-            );
756
-        }
757
-    }
758
-
759
-
760
-    /**
761
-     * Returns true if the query_param_key was a logic query parameter, eg `OR`, `AND`, `NOT`, `OR*`, etc.
762
-     *
763
-     * @return boolean
764
-     * @since 4.9.72.p
765
-     */
766
-    private function isLogicQueryParam(): bool
767
-    {
768
-        return in_array($this->getQueryParamKeySansStars(), $this->getContext()->getModel()->logic_query_param_keys());
769
-    }
770
-
771
-
772
-    /**
773
-     * If the query param isn't for a field, it must be a nested query parameter which requires different logic.
774
-     *
775
-     * @return array
776
-     * @throws DomainException
777
-     * @throws EE_Error
778
-     * @throws RestException
779
-     * @throws InvalidDataTypeException
780
-     * @throws InvalidInterfaceException
781
-     * @throws InvalidArgumentException
782
-     * @since 4.9.72.p
783
-     */
784
-    public function determineNestedConditionQueryParameters(): ?array
785
-    {
786
-        // so this param doesn't correspond to a field eh?
787
-        if ($this->getContext()->isWriting()) {
788
-            // always tell API clients about invalid parameters when they're creating data. Otherwise,
789
-            // they are probably going to create invalid data
790
-            throw new RestException(
791
-                'invalid_field',
792
-                sprintf(
793
-                /* translators: 1: variable name */
794
-                    esc_html__('You have provided an invalid parameter: "%1$s"', 'event_espresso'),
795
-                    $this->getQueryParamKey()
796
-                )
797
-            );
798
-        }
799
-        // so it's not for a field, is it a logic query param key?
800
-        if ($this->isLogicQueryParam()) {
801
-            return ModelDataTranslator::prepareConditionsQueryParamsForModels(
802
-                $this->getQueryParamValue(),
803
-                $this->getContext()->getModel(),
804
-                $this->getContext()->getRequestedVersion()
805
-            );
806
-        }
807
-        if (EED_Core_Rest_Api::debugMode()) {
808
-            // only tell API clients they got it wrong if we're in debug mode
809
-            // otherwise try our best ot fulfill their request by ignoring this invalid data
810
-            throw new RestException(
811
-                'invalid_parameter',
812
-                sprintf(
813
-                /* translators: 1: variable name */
814
-                    esc_html__(
815
-                        'You provided an invalid parameter, with key "%1$s"',
816
-                        'event_espresso'
817
-                    ),
818
-                    $this->getQueryParamKey()
819
-                ),
820
-                [
821
-                    'status' => 400,
822
-                ]
823
-            );
824
-        }
825
-        return null;
826
-    }
32
+	/**
33
+	 * @var string
34
+	 */
35
+	private $query_param_key;
36
+
37
+	/**
38
+	 * @var mixed
39
+	 */
40
+	private $query_param_value;
41
+
42
+	/**
43
+	 * @var RestIncomingQueryParamContext
44
+	 */
45
+	private $context;
46
+
47
+	/**
48
+	 * @var EE_Model_Field_Base|null
49
+	 */
50
+	private $field;
51
+
52
+	/**
53
+	 * @var string same as $query_param_key but has the * and anything after it removed
54
+	 */
55
+	private $query_param_key_sans_stars;
56
+
57
+	/**
58
+	 * @var string for timezone or timezone offset
59
+	 */
60
+	private $timezone;
61
+
62
+	/**
63
+	 * @var boolean if the field in $query_param_key is for a GMT field (eg `EVT_modified_gmt`)
64
+	 */
65
+	private $is_gmt_field = false;
66
+
67
+
68
+	/**
69
+	 * RestIncomingQueryParamMetadata constructor.
70
+	 * You probably want to call
71
+	 *
72
+	 * @param string                        $query_param_key
73
+	 * @param mixed                         $query_param_value
74
+	 * @param RestIncomingQueryParamContext $context
75
+	 * @throws EE_Error
76
+	 * @throws RestException
77
+	 * @throws ReflectionException
78
+	 */
79
+	public function __construct(string $query_param_key, $query_param_value, RestIncomingQueryParamContext $context)
80
+	{
81
+		$this->query_param_key   = $query_param_key;
82
+		$this->query_param_value = $query_param_value;
83
+		$this->context           = $context;
84
+		$this->determineFieldAndTimezone();
85
+	}
86
+
87
+
88
+	/**
89
+	 * Gets the query parameter key. This may have been modified (see setQueryParamValue())
90
+	 *
91
+	 * @return string
92
+	 */
93
+	public function getQueryParamKey(): string
94
+	{
95
+		return $this->query_param_key;
96
+	}
97
+
98
+
99
+	/**
100
+	 * Modifies the query parameter key passed in (Eg this is done when rewriting the simplified specified operator REST
101
+	 * query parameters into the legacy structure)
102
+	 *
103
+	 * @param string|array|int|float $query_param_value
104
+	 */
105
+	private function setQueryParamValue($query_param_value)
106
+	{
107
+		$this->query_param_value = $query_param_value;
108
+	}
109
+
110
+
111
+	/**
112
+	 * Gets the original query parameter value passed in.
113
+	 *
114
+	 * @return mixed
115
+	 */
116
+	public function getQueryParamValue()
117
+	{
118
+		return $this->query_param_value;
119
+	}
120
+
121
+
122
+	/**
123
+	 * Gets the context object.
124
+	 *
125
+	 * @return RestIncomingQueryParamContext
126
+	 */
127
+	public function getContext(): RestIncomingQueryParamContext
128
+	{
129
+		return $this->context;
130
+	}
131
+
132
+
133
+	/**
134
+	 * Sets the query parameter key. This may be used to rewrite a key into its non-GMT alternative.
135
+	 *
136
+	 * @param string $query_param_key
137
+	 */
138
+	private function setQueryParamKey(string $query_param_key)
139
+	{
140
+		$this->query_param_key = $query_param_key;
141
+	}
142
+
143
+
144
+	/**
145
+	 * Gets the field the query parameter key indicated. This may be null (in cases where the query parameter key
146
+	 * did not indicate a field, eg if it were `OR`).
147
+	 *
148
+	 * @return EE_Model_Field_Base|null
149
+	 */
150
+	public function getField(): ?EE_Model_Field_Base
151
+	{
152
+		return $this->field;
153
+	}
154
+
155
+
156
+	/**
157
+	 * Gets the query parameter key (with the star and everything afterwards removed).
158
+	 *
159
+	 * @return string
160
+	 */
161
+	public function getQueryParamKeySansStars(): string
162
+	{
163
+		return $this->query_param_key_sans_stars;
164
+	}
165
+
166
+
167
+	/**
168
+	 * Gets the timezone associated with this model (the site timezone, except for GMT datetime fields).
169
+	 *
170
+	 * @return string
171
+	 */
172
+	public function getTimezone(): string
173
+	{
174
+		return $this->timezone;
175
+	}
176
+
177
+
178
+	/**
179
+	 * Returns whether or not this is a GMT field
180
+	 *
181
+	 * @return boolean
182
+	 */
183
+	public function isGmtField(): bool
184
+	{
185
+		return $this->is_gmt_field;
186
+	}
187
+
188
+
189
+	/**
190
+	 * Sets the field indicated by the query parameter key (might be null).
191
+	 *
192
+	 * @param EE_Model_Field_Base|null $field
193
+	 */
194
+	private function setField(EE_Model_Field_Base $field = null)
195
+	{
196
+		$this->field = $field;
197
+	}
198
+
199
+
200
+	/**
201
+	 * Sets the query parameter key-with-stars-removed.
202
+	 *
203
+	 * @param string $query_param_key_sans_stars
204
+	 */
205
+	private function setQueryParamKeySansStars(string $query_param_key_sans_stars)
206
+	{
207
+		$this->query_param_key_sans_stars = $query_param_key_sans_stars;
208
+	}
209
+
210
+
211
+	/**
212
+	 * Sets the timezone (this could be a timezone offset string).
213
+	 *
214
+	 * @param string $timezone
215
+	 */
216
+	private function setTimezone(string $timezone)
217
+	{
218
+		$this->timezone = $timezone;
219
+	}
220
+
221
+
222
+	/**
223
+	 * @param bool|int|string $is_gmt_field
224
+	 */
225
+	private function setIsGmtField($is_gmt_field)
226
+	{
227
+		$this->is_gmt_field = filter_var($is_gmt_field, FILTER_VALIDATE_BOOLEAN);
228
+	}
229
+
230
+
231
+	/**
232
+	 * Determines what field, query param name, and query param name without stars, and timezone to use.
233
+	 *
234
+	 * @return void {
235
+	 * @throws EE_Error
236
+	 * @throws RestException
237
+	 * @throws ReflectionException
238
+	 * @since 4.9.72.p
239
+	 *
240
+	 */
241
+	private function determineFieldAndTimezone()
242
+	{
243
+		$this->setQueryParamKeySansStars(
244
+			ModelDataTranslator::removeStarsAndAnythingAfterFromConditionQueryParamKey(
245
+				$this->getQueryParamKey()
246
+			)
247
+		);
248
+		$this->setField(
249
+			ModelDataTranslator::deduceFieldFromQueryParam(
250
+				$this->getQueryParamKeySansStars(),
251
+				$this->getContext()->getModel()
252
+			)
253
+		);
254
+		// double-check is it a *_gmt field?
255
+		if (
256
+			! $this->getField() instanceof EE_Model_Field_Base
257
+			&& ModelDataTranslator::isGmtDateFieldName($this->getQueryParamKeySansStars())
258
+		) {
259
+			// yep, take off '_gmt', and find the field
260
+			$this->setQueryParamKey(ModelDataTranslator::removeGmtFromFieldName($this->getQueryParamKeySansStars()));
261
+			$this->setField(
262
+				ModelDataTranslator::deduceFieldFromQueryParam(
263
+					$this->getQueryParamKey(),
264
+					$this->context->getModel()
265
+				)
266
+			);
267
+			$this->setTimezone('UTC');
268
+			$this->setIsGmtField(true);
269
+		} elseif ($this->getField() instanceof EE_Datetime_Field) {
270
+			// so it's not a GMT field. Set the timezone on the model to the default
271
+			$this->setTimezone(EEH_DTT_Helper::get_valid_timezone_string());
272
+		} else {
273
+			// just keep using what's already set for the timezone
274
+			$this->setTimezone($this->context->getModel()->get_timezone());
275
+		}
276
+		$this->assertOnlyAdminCanReadPasswordFields();
277
+	}
278
+
279
+
280
+	/**
281
+	 * Throws an exception if a non-admin is trying to query by password.
282
+	 *
283
+	 * @throws RestException
284
+	 * @since 4.9.74.p
285
+	 */
286
+	private function assertOnlyAdminCanReadPasswordFields()
287
+	{
288
+		if (
289
+			$this->getField() instanceof EE_Password_Field
290
+			&& ! current_user_can(EE_Restriction_Generator_Base::get_default_restrictions_cap())
291
+		) {
292
+			// only full admins can query by password. sorry bub!
293
+			throw new RestException(
294
+				'only_admins_can_query_by_password',
295
+				// @codingStandardsIgnoreStart
296
+				esc_html__(
297
+					'You attempted to filter by a password field without the needed privileges. Only a full admin is allowed to do that.',
298
+					'event_espresso'
299
+				),
300
+				// @codingStandardsIgnoreEnd
301
+				[
302
+					'status' => 403,
303
+				]
304
+			);
305
+		}
306
+	}
307
+
308
+
309
+	/**
310
+	 * Given a ton of input, determines the value to use for the models.
311
+	 *
312
+	 * @return array|null
313
+	 * @throws DomainException
314
+	 * @throws EE_Error
315
+	 * @throws RestException
316
+	 * @throws DomainException
317
+	 * @since 4.9.72.p
318
+	 */
319
+	public function determineConditionsQueryParameterValue(): ?array
320
+	{
321
+		if ($this->valueIsArrayDuringRead()) {
322
+			return $this->determineModelValueGivenRestInputArray();
323
+		}
324
+		return ModelDataTranslator::prepareFieldValueFromJson(
325
+			$this->getField(),
326
+			$this->getQueryParamValue(),
327
+			$this->getContext()->getRequestedVersion(),
328
+			$this->getTimezone()
329
+		);
330
+	}
331
+
332
+
333
+	/**
334
+	 * Given that the array value provided was itself an array, handles finding the correct value to pass to the model.
335
+	 *
336
+	 * @return array|null
337
+	 * @throws EE_Error
338
+	 * @throws RestException
339
+	 * @since 4.9.72.p
340
+	 */
341
+	private function determineModelValueGivenRestInputArray(): ?array
342
+	{
343
+		$this->transformSimplifiedSpecifiedOperatorSyntaxIntoStandardSyntax();
344
+		// did they specify an operator?
345
+		if ($this->valueIsLegacySpecifiedOperator()) {
346
+			$query_param_value = $this->getQueryParamValue();
347
+			$sub_array_key     = $query_param_value[0];
348
+			$translated_value  = [$sub_array_key];
349
+			if ($this->operatorIsNAry($sub_array_key)) {
350
+				$translated_value[] = $this->prepareValuesFromJson($query_param_value[1]);
351
+			} elseif ($this->operatorIsTernary($sub_array_key)) {
352
+				$translated_value[] = [
353
+					$this->prepareValuesFromJson($query_param_value[1][0]),
354
+					$this->prepareValuesFromJson($query_param_value[1][1]),
355
+				];
356
+			} elseif ($this->operatorIsLike($sub_array_key)) {
357
+				// we want to leave this value mostly-as-is (eg don't force it to be a float
358
+				// or a boolean or an enum value. Leave it as-is with wildcards etc)
359
+				// but do verify it at least doesn't have any serialized data
360
+				ModelDataTranslator::throwExceptionIfContainsSerializedData($query_param_value[1]);
361
+				$translated_value[] = $query_param_value[1];
362
+			} elseif ($this->operatorIsUnary($sub_array_key)) {
363
+				// no arguments should have been provided, so don't look for any
364
+			} elseif ($this->operatorIsBinary($sub_array_key)) {
365
+				// it's a valid operator, but none of the exceptions. Treat it normally.
366
+				$translated_value[] = $this->prepareValuesFromJson($query_param_value[1]);
367
+			} else {
368
+				// so they provided a valid operator, but wrong number of arguments
369
+				$this->throwWrongNumberOfArgsExceptionIfDebugging($sub_array_key);
370
+				$translated_value = null;
371
+			}
372
+		} else {
373
+			// so they didn't provide a valid operator
374
+			// if we aren't in debug mode, then just try our best to fulfill the user's request
375
+			$this->throwInvalidOperatorExceptionIfDebugging();
376
+			$translated_value = null;
377
+		}
378
+		return $translated_value;
379
+	}
380
+
381
+
382
+	/**
383
+	 * Returns if this request is a "read" request and the value provided was an array.
384
+	 * This will indicate is such things as `array('<', 123)` and `array('IN', array(1,2,3))` are acceptable or not.
385
+	 *
386
+	 * @return boolean
387
+	 * @since 4.9.72.p
388
+	 */
389
+	private function valueIsArrayDuringRead(): bool
390
+	{
391
+		return ! $this->getContext()->isWriting() && is_array($this->getQueryParamValue());
392
+	}
393
+
394
+
395
+	/**
396
+	 * Returns if the value provided was an associative array (we should have already verified it's an array of some
397
+	 * sort). If the value is an associative array, it had better be in the simplified specified operator structure.
398
+	 *
399
+	 * @return boolean
400
+	 * @since 4.9.72.p
401
+	 */
402
+	private function valueIsAssociativeArray(): bool
403
+	{
404
+		return ! EEH_Array::is_array_numerically_and_sequentially_indexed((array) $this->getQueryParamValue());
405
+	}
406
+
407
+
408
+	/**
409
+	 * Checks if the array value is itself an array that fits into the simplified specified operator structure
410
+	 * (eg `array('!=' => 123)`).
411
+	 *
412
+	 * @return boolean
413
+	 * @since 4.9.72.p
414
+	 */
415
+	private function valueIsSimplifiedSpecifiedOperator(): bool
416
+	{
417
+		return count($this->getQueryParamValue()) === 1
418
+			   && array_key_exists(
419
+				   key($this->getQueryParamValue()),
420
+				   $this->getContext()->getModel()->valid_operators()
421
+			   );
422
+	}
423
+
424
+
425
+	/**
426
+	 * Throws an exception if the sub-value is an array (eg `array('!=' => array())`). It needs to just be a string,
427
+	 * of either comma-separated-values, or a JSON array.
428
+	 *
429
+	 * @param $sub_array_key
430
+	 * @param $sub_array_value
431
+	 * @throws RestException
432
+	 * @since 4.9.72.p
433
+	 */
434
+	private function assertSubValueIsNotArray($sub_array_key, $sub_array_value)
435
+	{
436
+		if (is_array($sub_array_value) && EED_Core_Rest_Api::debugMode()) {
437
+			throw new RestException(
438
+				'csv_or_json_string_only',
439
+				sprintf(
440
+				/* translators: 1: variable name*/
441
+					esc_html__(
442
+						'The value provided for the operator "%1$s" should be comma-separated value string or a JSON array.',
443
+						'event_espresso'
444
+					),
445
+					$sub_array_key
446
+				),
447
+				[
448
+					'status' => 400,
449
+				]
450
+			);
451
+		}
452
+	}
453
+
454
+
455
+	/**
456
+	 * Determines if the sub-array key is an operator taking 3 or more operators.
457
+	 *
458
+	 * @param $sub_array_key
459
+	 * @return boolean
460
+	 * @since 4.9.72.p
461
+	 */
462
+	private function subArrayKeyIsNonBinaryOperator($sub_array_key): bool
463
+	{
464
+		return array_key_exists(
465
+			$sub_array_key,
466
+			array_merge(
467
+				$this->getContext()->getModel()->valid_in_style_operators(),
468
+				$this->getContext()->getModel()->valid_between_style_operators()
469
+			)
470
+		);
471
+	}
472
+
473
+
474
+	/**
475
+	 * Given that the $sub_array_key is a string, checks if it's an operator taking only 1 argument.
476
+	 *
477
+	 * @param string $sub_array_key
478
+	 * @return boolean
479
+	 * @since 4.9.72.p
480
+	 */
481
+	private function subArrayKeyIsUnaryOperator(string $sub_array_key): bool
482
+	{
483
+		return array_key_exists(
484
+			$sub_array_key,
485
+			$this->getContext()->getModel()->valid_null_style_operators()
486
+		);
487
+	}
488
+
489
+
490
+	/**
491
+	 * Parses the $sub_array_value string into an array (given it could either be a comma-separated-list or a JSON
492
+	 * array). eg `"1,2,3"` or `"[1,2,3]"` into `array(1,2,3)`.
493
+	 *
494
+	 * @param $sub_array_value
495
+	 * @return array|mixed|object
496
+	 * @since 4.9.72.p
497
+	 */
498
+	private function extractQuickStyleSpecifiedOperatorValue($sub_array_value)
499
+	{
500
+		// the value should be JSON or CSV
501
+		$values = json_decode($sub_array_value);
502
+		if (! is_array($values)) {
503
+			$values = array_filter(
504
+				array_map(
505
+					'trim',
506
+					explode(
507
+						',',
508
+						$sub_array_value
509
+					)
510
+				)
511
+			);
512
+		}
513
+		return $values;
514
+	}
515
+
516
+
517
+	/**
518
+	 * Throws an exception if the value isn't a simplified specified operator (only called when we expect that).
519
+	 *
520
+	 * @throws RestException
521
+	 * @since 4.9.72.p
522
+	 */
523
+	private function assertSimplifiedSpecifiedOperator()
524
+	{
525
+		if (! $this->valueIsSimplifiedSpecifiedOperator() && EED_Core_Rest_Api::debugMode()) {
526
+			throw new RestException(
527
+				'numerically_indexed_array_of_values_only',
528
+				sprintf(
529
+				/* translators: 1: variable name*/
530
+					esc_html__(
531
+						'The array provided for the parameter "%1$s" should be numerically indexed.',
532
+						'event_espresso'
533
+					),
534
+					$this->getQueryParamKey()
535
+				),
536
+				[
537
+					'status' => 400,
538
+				]
539
+			);
540
+		}
541
+	}
542
+
543
+
544
+	/**
545
+	 * If query_param_value were in the simplified specific operator structure, change it into the legacy structure.
546
+	 *
547
+	 * @throws RestException
548
+	 * @since 4.9.72.p
549
+	 */
550
+	private function transformSimplifiedSpecifiedOperatorSyntaxIntoStandardSyntax()
551
+	{
552
+		if ($this->valueIsAssociativeArray()) {
553
+			$this->assertSimplifiedSpecifiedOperator();
554
+			$query_param_value = $this->getQueryParamValue();
555
+			$sub_array_value   = reset($query_param_value);
556
+			$sub_array_key     = key($query_param_value);
557
+			$this->assertSubValueIsNotArray($sub_array_key, $sub_array_value);
558
+			// they're doing something like "&where[EVT_ID][IN]=1,2,3" or "&where[EVT_ID][>]=5"
559
+			if ($this->subArrayKeyIsNonBinaryOperator($sub_array_key)) {
560
+				$this->setQueryParamValue([
561
+											  $sub_array_key,
562
+											  $this->extractQuickStyleSpecifiedOperatorValue($sub_array_value),
563
+										  ]);
564
+			} elseif ($this->subArrayKeyIsUnaryOperator($sub_array_key)) {
565
+				$this->setQueryParamValue([$sub_array_key]);
566
+			} else {
567
+				$this->setQueryParamValue([$sub_array_key, $sub_array_value]);
568
+			}
569
+		}
570
+	}
571
+
572
+
573
+	/**
574
+	 * Returns true is the value is an array using the legacy structure to specify the operator. Eg `array('!=',123)`.
575
+	 *
576
+	 * @return boolean
577
+	 * @since 4.9.72.p
578
+	 */
579
+	private function valueIsLegacySpecifiedOperator(): bool
580
+	{
581
+		$valid_operators   = $this->getContext()->getModel()->valid_operators();
582
+		$query_param_value = $this->getQueryParamValue();
583
+		return isset($query_param_value[0])
584
+			   && isset($valid_operators[ $query_param_value[0] ]);
585
+	}
586
+
587
+
588
+	/**
589
+	 * Returns true if the value specified operator accepts arbitrary number of arguments, like "IN".
590
+	 *
591
+	 * @param $operator
592
+	 * @return boolean
593
+	 * @since 4.9.72.p
594
+	 */
595
+	private function operatorIsNAry($operator): bool
596
+	{
597
+		$valueArray = $this->getQueryParamValue();
598
+		return array_key_exists(
599
+				   $operator,
600
+				   $this->getContext()->getModel()->valid_in_style_operators()
601
+			   )
602
+			   && isset($valueArray[1])
603
+			   && is_array($valueArray[1])
604
+			   && ! isset($valueArray[2]);
605
+	}
606
+
607
+
608
+	/**
609
+	 * Returns true if the operator accepts 3 arguments (eg "BETWEEN").
610
+	 * So we're looking for a value that looks like
611
+	 * `array('BETWEEN', array('2015-01-01T00:00:00', '2016-01-01T00:00:00'))`.
612
+	 *
613
+	 * @param $operator
614
+	 * @return boolean
615
+	 * @since 4.9.72.p
616
+	 */
617
+	private function operatorIsTernary($operator): bool
618
+	{
619
+		$query_param_value = $this->getQueryParamValue();
620
+		return array_key_exists($operator, $this->getContext()->getModel()->valid_between_style_operators())
621
+			   && isset($query_param_value[1])
622
+			   && is_array($query_param_value[1])
623
+			   && isset($query_param_value[1][0], $query_param_value[1][1])
624
+			   && ! isset($query_param_value[1][2])
625
+			   && ! isset($query_param_value[2]);
626
+	}
627
+
628
+
629
+	/**
630
+	 * Returns true if the operator is a similar to LIKE, indicating the value may have wildcards we should leave alone.
631
+	 *
632
+	 * @param $operator
633
+	 * @return boolean
634
+	 * @since 4.9.72.p
635
+	 */
636
+	private function operatorIsLike($operator): bool
637
+	{
638
+		$query_param_value = $this->getQueryParamValue();
639
+		return array_key_exists($operator, $this->getContext()->getModel()->valid_like_style_operators())
640
+			   && isset($query_param_value[1])
641
+			   && ! isset($query_param_value[2]);
642
+	}
643
+
644
+
645
+	/**
646
+	 * Returns true if the operator only takes one argument (eg it's like `IS NULL`).
647
+	 *
648
+	 * @param $operator
649
+	 * @return boolean
650
+	 * @since 4.9.72.p
651
+	 */
652
+	private function operatorIsUnary($operator): bool
653
+	{
654
+		$query_param_value = $this->getQueryParamValue();
655
+		return array_key_exists($operator, $this->getContext()->getModel()->valid_null_style_operators())
656
+			   && ! isset($query_param_value[1]);
657
+	}
658
+
659
+
660
+	/**
661
+	 * Returns true if the operator specified is a binary operator (eg `=`, `!=`)
662
+	 *
663
+	 * @param $operator
664
+	 * @return boolean
665
+	 * @since 4.9.72.p
666
+	 */
667
+	private function operatorIsBinary($operator): bool
668
+	{
669
+		$query_param_value = $this->getQueryParamValue();
670
+		$model             = $this->getContext()->getModel();
671
+		return isset($query_param_value[1])
672
+			   && ! isset($query_param_value[2])
673
+			   && ! array_key_exists(
674
+				$operator,
675
+				array_merge(
676
+					$model->valid_in_style_operators(),
677
+					$model->valid_null_style_operators(),
678
+					$model->valid_like_style_operators(),
679
+					$model->valid_between_style_operators()
680
+				)
681
+			);
682
+	}
683
+
684
+
685
+	/**
686
+	 * If we're debugging, throws an exception saying that the wrong number of arguments was provided.
687
+	 *
688
+	 * @param $operator
689
+	 * @throws RestException
690
+	 * @since 4.9.72.p
691
+	 */
692
+	private function throwWrongNumberOfArgsExceptionIfDebugging($operator)
693
+	{
694
+		if (EED_Core_Rest_Api::debugMode()) {
695
+			throw new RestException(
696
+				'wrong_number_of_arguments',
697
+				sprintf(
698
+					esc_html__(
699
+						'The operator you provided, "%1$s" had the wrong number of arguments',
700
+						'event_espresso'
701
+					),
702
+					$operator
703
+				),
704
+				[
705
+					'status' => 400,
706
+				]
707
+			);
708
+		}
709
+	}
710
+
711
+
712
+	/**
713
+	 * Wrapper for ModelDataTranslator::prepareFieldValuesFromJson(), just a tad more DRY.
714
+	 *
715
+	 * @param $value
716
+	 * @return mixed
717
+	 * @throws EE_Error
718
+	 * @throws RestException
719
+	 * @since 4.9.72.p
720
+	 */
721
+	private function prepareValuesFromJson($value)
722
+	{
723
+		return ModelDataTranslator::prepareFieldValuesFromJson(
724
+			$this->getField(),
725
+			$value,
726
+			$this->getContext()->getRequestedVersion(),
727
+			$this->getTimezone()
728
+		);
729
+	}
730
+
731
+
732
+	/**
733
+	 * Throws an exception if an invalid operator was specified and we're debugging.
734
+	 *
735
+	 * @throws RestException
736
+	 * @since 4.9.72.p
737
+	 */
738
+	private function throwInvalidOperatorExceptionIfDebugging()
739
+	{
740
+		// so they didn't provide a valid operator
741
+		if (EED_Core_Rest_Api::debugMode()) {
742
+			throw new RestException(
743
+				'invalid_operator',
744
+				sprintf(
745
+					esc_html__(
746
+						'You provided an invalid parameter, with key "%1$s" and value "%2$s"',
747
+						'event_espresso'
748
+					),
749
+					$this->getQueryParamKey(),
750
+					$this->getQueryParamValue()
751
+				),
752
+				[
753
+					'status' => 400,
754
+				]
755
+			);
756
+		}
757
+	}
758
+
759
+
760
+	/**
761
+	 * Returns true if the query_param_key was a logic query parameter, eg `OR`, `AND`, `NOT`, `OR*`, etc.
762
+	 *
763
+	 * @return boolean
764
+	 * @since 4.9.72.p
765
+	 */
766
+	private function isLogicQueryParam(): bool
767
+	{
768
+		return in_array($this->getQueryParamKeySansStars(), $this->getContext()->getModel()->logic_query_param_keys());
769
+	}
770
+
771
+
772
+	/**
773
+	 * If the query param isn't for a field, it must be a nested query parameter which requires different logic.
774
+	 *
775
+	 * @return array
776
+	 * @throws DomainException
777
+	 * @throws EE_Error
778
+	 * @throws RestException
779
+	 * @throws InvalidDataTypeException
780
+	 * @throws InvalidInterfaceException
781
+	 * @throws InvalidArgumentException
782
+	 * @since 4.9.72.p
783
+	 */
784
+	public function determineNestedConditionQueryParameters(): ?array
785
+	{
786
+		// so this param doesn't correspond to a field eh?
787
+		if ($this->getContext()->isWriting()) {
788
+			// always tell API clients about invalid parameters when they're creating data. Otherwise,
789
+			// they are probably going to create invalid data
790
+			throw new RestException(
791
+				'invalid_field',
792
+				sprintf(
793
+				/* translators: 1: variable name */
794
+					esc_html__('You have provided an invalid parameter: "%1$s"', 'event_espresso'),
795
+					$this->getQueryParamKey()
796
+				)
797
+			);
798
+		}
799
+		// so it's not for a field, is it a logic query param key?
800
+		if ($this->isLogicQueryParam()) {
801
+			return ModelDataTranslator::prepareConditionsQueryParamsForModels(
802
+				$this->getQueryParamValue(),
803
+				$this->getContext()->getModel(),
804
+				$this->getContext()->getRequestedVersion()
805
+			);
806
+		}
807
+		if (EED_Core_Rest_Api::debugMode()) {
808
+			// only tell API clients they got it wrong if we're in debug mode
809
+			// otherwise try our best ot fulfill their request by ignoring this invalid data
810
+			throw new RestException(
811
+				'invalid_parameter',
812
+				sprintf(
813
+				/* translators: 1: variable name */
814
+					esc_html__(
815
+						'You provided an invalid parameter, with key "%1$s"',
816
+						'event_espresso'
817
+					),
818
+					$this->getQueryParamKey()
819
+				),
820
+				[
821
+					'status' => 400,
822
+				]
823
+			);
824
+		}
825
+		return null;
826
+	}
827 827
 }
828 828
 // End of file RestQueryParamMetadata.php
829 829
 // Location: EventEspresso\core\libraries\rest_api/RestQueryParamMetadata.php
Please login to merge, or discard this patch.
modules/core_rest_api/EED_Core_Rest_Api.module.php 2 patches
Indentation   +1381 added lines, -1381 removed lines patch added patch discarded remove patch
@@ -21,1385 +21,1385 @@
 block discarded – undo
21 21
  */
22 22
 class EED_Core_Rest_Api extends EED_Module
23 23
 {
24
-    const ee_api_namespace           = Domain::API_NAMESPACE;
25
-
26
-    const ee_api_namespace_for_regex = 'ee\/v([^/]*)\/';
27
-
28
-    const saved_routes_option_names  = 'ee_core_routes';
29
-
30
-    /**
31
-     * string used in _links response bodies to make them globally unique.
32
-     *
33
-     * @see http://v2.wp-api.org/extending/linking/
34
-     */
35
-    const ee_api_link_namespace = 'https://api.eventespresso.com/';
36
-
37
-    /**
38
-     * @var CalculatedModelFields
39
-     */
40
-    protected static $_field_calculator;
41
-
42
-
43
-    /**
44
-     * @return EED_Core_Rest_Api|EED_Module
45
-     * @throws EE_Error
46
-     * @throws ReflectionException
47
-     */
48
-    public static function instance()
49
-    {
50
-        self::$_field_calculator =
51
-            LoaderFactory::getLoader()->load('EventEspresso\core\libraries\rest_api\CalculatedModelFields');
52
-        return parent::get_instance(__CLASS__);
53
-    }
54
-
55
-
56
-    /**
57
-     *    set_hooks - for hooking into EE Core, other modules, etc
58
-     *
59
-     * @access    public
60
-     * @return    void
61
-     */
62
-    public static function set_hooks()
63
-    {
64
-        self::set_hooks_both();
65
-    }
66
-
67
-
68
-    /**
69
-     *    set_hooks_admin - for hooking into EE Admin Core, other modules, etc
70
-     *
71
-     * @access    public
72
-     * @return    void
73
-     */
74
-    public static function set_hooks_admin()
75
-    {
76
-        self::set_hooks_both();
77
-    }
78
-
79
-
80
-    public static function set_hooks_both()
81
-    {
82
-        add_action('rest_api_init', ['EED_Core_Rest_Api', 'set_hooks_rest_api'], 5);
83
-        add_action('rest_api_init', ['EED_Core_Rest_Api', 'register_routes'], 10);
84
-        add_filter('rest_route_data', ['EED_Core_Rest_Api', 'hide_old_endpoints'], 10, 2);
85
-        add_filter(
86
-            'rest_index',
87
-            ['EventEspresso\core\libraries\rest_api\controllers\model\Meta', 'filterEeMetadataIntoIndex']
88
-        );
89
-        EED_Core_Rest_Api::invalidate_cached_route_data_on_version_change();
90
-    }
91
-
92
-
93
-    /**
94
-     * sets up hooks which only need to be included as part of REST API requests;
95
-     * other requests like to the frontend or admin etc don't need them
96
-     *
97
-     * @throws EE_Error
98
-     */
99
-    public static function set_hooks_rest_api()
100
-    {
101
-        // set hooks which account for changes made to the API
102
-        EED_Core_Rest_Api::_set_hooks_for_changes();
103
-    }
104
-
105
-
106
-    /**
107
-     * public wrapper of _set_hooks_for_changes.
108
-     * Loads all the hooks which make requests to old versions of the API
109
-     * appear the same as they always did
110
-     *
111
-     * @throws EE_Error
112
-     */
113
-    public static function set_hooks_for_changes()
114
-    {
115
-        self::_set_hooks_for_changes();
116
-    }
117
-
118
-
119
-    /**
120
-     * Loads all the hooks which make requests to old versions of the API
121
-     * appear the same as they always did
122
-     *
123
-     * @throws EE_Error
124
-     */
125
-    protected static function _set_hooks_for_changes()
126
-    {
127
-        $folder_contents = EEH_File::get_contents_of_folders([EE_LIBRARIES . 'rest_api/changes']);
128
-        foreach ($folder_contents as $classname_in_namespace => $filepath) {
129
-            // ignore the base parent class
130
-            // and legacy named classes
131
-            if (
132
-                $classname_in_namespace === 'ChangesInBase'
133
-                || strpos($classname_in_namespace, 'Changes_In_') === 0
134
-            ) {
135
-                continue;
136
-            }
137
-            $full_classname = 'EventEspresso\core\libraries\rest_api\changes\\' . $classname_in_namespace;
138
-            if (class_exists($full_classname)) {
139
-                $instance_of_class = new $full_classname();
140
-                if ($instance_of_class instanceof ChangesInBase) {
141
-                    $instance_of_class->setHooks();
142
-                }
143
-            }
144
-        }
145
-    }
146
-
147
-
148
-    /**
149
-     * Filters the WP routes to add our EE-related ones. This takes a bit of time
150
-     * so we actually prefer to only do it when an EE plugin is activated or upgraded
151
-     *
152
-     * @throws EE_Error
153
-     * @throws ReflectionException
154
-     */
155
-    public static function register_routes()
156
-    {
157
-        foreach (EED_Core_Rest_Api::get_ee_route_data() as $namespace => $relative_routes) {
158
-            foreach ($relative_routes as $relative_route => $data_for_multiple_endpoints) {
159
-                /**
160
-                 * @var array     $data_for_multiple_endpoints numerically indexed array
161
-                 *                                         but can also contain route options like {
162
-                 * @type array    $schema                      {
163
-                 * @type callable $schema_callback
164
-                 * @type array    $callback_args               arguments that will be passed to the callback, after the
165
-                 * WP_REST_Request of course
166
-                 * }
167
-                 * }
168
-                 */
169
-                // when registering routes, register all the endpoints' data at the same time
170
-                $multiple_endpoint_args = [];
171
-                foreach ($data_for_multiple_endpoints as $endpoint_key => $data_for_single_endpoint) {
172
-                    /**
173
-                     * @var array     $data_for_single_endpoint {
174
-                     * @type callable $callback
175
-                     * @type string methods
176
-                     * @type array args
177
-                     * @type array _links
178
-                     * @type array    $callback_args            arguments that will be passed to the callback, after the
179
-                     * WP_REST_Request of course
180
-                     * }
181
-                     */
182
-                    // skip route options
183
-                    if (! is_numeric($endpoint_key)) {
184
-                        continue;
185
-                    }
186
-                    if (! isset($data_for_single_endpoint['callback'], $data_for_single_endpoint['methods'])) {
187
-                        throw new EE_Error(
188
-                            esc_html__(
189
-                            // @codingStandardsIgnoreStart
190
-                                'Endpoint configuration data needs to have entries "callback" (callable) and "methods" (comma-separated list of accepts HTTP methods).',
191
-                                // @codingStandardsIgnoreEnd
192
-                                'event_espresso'
193
-                            )
194
-                        );
195
-                    }
196
-                    $callback             = $data_for_single_endpoint['callback'];
197
-                    $single_endpoint_args = [
198
-                        'methods' => $data_for_single_endpoint['methods'],
199
-                        'args'    => $data_for_single_endpoint['args'] ?? [],
200
-                    ];
201
-                    if (isset($data_for_single_endpoint['_links'])) {
202
-                        $single_endpoint_args['_links'] = $data_for_single_endpoint['_links'];
203
-                    }
204
-                    if (isset($data_for_single_endpoint['callback_args'])) {
205
-                        $callback_args                    = $data_for_single_endpoint['callback_args'];
206
-                        $single_endpoint_args['callback'] = function (WP_REST_Request $request) use (
207
-                            $callback,
208
-                            $callback_args
209
-                        ) {
210
-                            array_unshift($callback_args, $request);
211
-                            return call_user_func_array(
212
-                                $callback,
213
-                                $callback_args
214
-                            );
215
-                        };
216
-                    } else {
217
-                        $single_endpoint_args['callback'] = $data_for_single_endpoint['callback'];
218
-                    }
219
-                    // As of WordPress 5.5, if a permission_callback is not provided,
220
-                    // the REST API will issue a _doing_it_wrong notice.
221
-                    // Since the EE REST API defers capabilities to the db model system,
222
-                    // we will just use the generic WP callback for public endpoints
223
-                    if (! isset($single_endpoint_args['permission_callback'])) {
224
-                        $single_endpoint_args['permission_callback'] = '__return_true';
225
-                    }
226
-                    $multiple_endpoint_args[] = $single_endpoint_args;
227
-                }
228
-                if (isset($data_for_multiple_endpoints['schema'])) {
229
-                    $schema_route_data                = $data_for_multiple_endpoints['schema'];
230
-                    $schema_callback                  = $schema_route_data['schema_callback'];
231
-                    $callback_args                    = $schema_route_data['callback_args'];
232
-                    $multiple_endpoint_args['schema'] = function () use ($schema_callback, $callback_args) {
233
-                        return call_user_func_array(
234
-                            $schema_callback,
235
-                            $callback_args
236
-                        );
237
-                    };
238
-                }
239
-                register_rest_route(
240
-                    $namespace,
241
-                    $relative_route,
242
-                    $multiple_endpoint_args
243
-                );
244
-            }
245
-        }
246
-    }
247
-
248
-
249
-    /**
250
-     * Checks if there was a version change or something that merits invalidating the cached
251
-     * route data. If so, invalidates the cached route data so that it gets refreshed
252
-     * next time the WP API is used
253
-     */
254
-    public static function invalidate_cached_route_data_on_version_change()
255
-    {
256
-        if (EE_System::instance()->detect_req_type() !== EE_System::req_type_normal) {
257
-            EED_Core_Rest_Api::invalidate_cached_route_data();
258
-        }
259
-        foreach (EE_Registry::instance()->addons as $addon) {
260
-            if ($addon instanceof EE_Addon && $addon->detect_req_type() !== EE_System::req_type_normal) {
261
-                EED_Core_Rest_Api::invalidate_cached_route_data();
262
-            }
263
-        }
264
-    }
265
-
266
-
267
-    /**
268
-     * Removes the cached route data so it will get refreshed next time the WP API is used
269
-     */
270
-    public static function invalidate_cached_route_data()
271
-    {
272
-        // delete the saved EE REST API routes
273
-        foreach (EED_Core_Rest_Api::versions_served() as $version => $hidden) {
274
-            delete_option(EED_Core_Rest_Api::saved_routes_option_names . $version);
275
-        }
276
-    }
277
-
278
-
279
-    /**
280
-     * Gets the EE route data
281
-     *
282
-     * @return array top-level key is the namespace, next-level key is the route and its value is array{
283
-     * @throws EE_Error
284
-     * @throws ReflectionException
285
-     * @type string|array $callback
286
-     * @type string       $methods
287
-     * @type boolean      $hidden_endpoint
288
-     * }
289
-     */
290
-    public static function get_ee_route_data(): array
291
-    {
292
-        $ee_routes = [];
293
-        foreach (self::versions_served() as $version => $hidden_endpoints) {
294
-            $ee_routes[ self::ee_api_namespace . $version ] = self::_get_ee_route_data_for_version(
295
-                $version,
296
-                $hidden_endpoints
297
-            );
298
-        }
299
-        return $ee_routes;
300
-    }
301
-
302
-
303
-    /**
304
-     * Gets the EE route data from the wp options if it exists already,
305
-     * otherwise re-generates it and saves it to the option
306
-     *
307
-     * @param string  $version
308
-     * @param boolean $hidden_endpoints
309
-     * @return array
310
-     * @throws EE_Error
311
-     * @throws ReflectionException
312
-     */
313
-    protected static function _get_ee_route_data_for_version(string $version, bool $hidden_endpoints = false): array
314
-    {
315
-        $ee_routes = get_option(self::saved_routes_option_names . $version, null);
316
-        if (! $ee_routes || EED_Core_Rest_Api::debugMode()) {
317
-            $ee_routes = self::_save_ee_route_data_for_version($version, $hidden_endpoints);
318
-        }
319
-        return $ee_routes;
320
-    }
321
-
322
-
323
-    /**
324
-     * Saves the EE REST API route data to a wp option and returns it
325
-     *
326
-     * @param string  $version
327
-     * @param boolean $hidden_endpoints
328
-     * @return mixed|null
329
-     * @throws EE_Error
330
-     * @throws ReflectionException
331
-     */
332
-    protected static function _save_ee_route_data_for_version(string $version, bool $hidden_endpoints = false)
333
-    {
334
-        $instance    = self::instance();
335
-        $routes      = apply_filters(
336
-            'EED_Core_Rest_Api__save_ee_route_data_for_version__routes',
337
-            array_replace_recursive(
338
-                $instance->_get_config_route_data_for_version($version, $hidden_endpoints),
339
-                $instance->_get_meta_route_data_for_version($version, $hidden_endpoints),
340
-                $instance->_get_model_route_data_for_version($version, $hidden_endpoints),
341
-                $instance->_get_rpc_route_data_for_version($version, $hidden_endpoints)
342
-            )
343
-        );
344
-        $option_name = self::saved_routes_option_names . $version;
345
-        if (get_option($option_name)) {
346
-            update_option($option_name, $routes, true);
347
-        } else {
348
-            add_option($option_name, $routes, null, 'no');
349
-        }
350
-        return $routes;
351
-    }
352
-
353
-
354
-    /**
355
-     * Calculates all the EE routes and saves it to a WordPress option so we don't
356
-     * need to calculate it on every request
357
-     *
358
-     * @return void
359
-     * @throws EE_Error
360
-     * @throws ReflectionException
361
-     * @deprecated since version 4.9.1
362
-     */
363
-    public static function save_ee_routes()
364
-    {
365
-        if (EE_Maintenance_Mode::instance()->models_can_query()) {
366
-            $instance = self::instance();
367
-            $routes   = apply_filters(
368
-                'EED_Core_Rest_Api__save_ee_routes__routes',
369
-                array_replace_recursive(
370
-                    $instance->_register_config_routes(),
371
-                    $instance->_register_meta_routes(),
372
-                    $instance->_register_model_routes(),
373
-                    $instance->_register_rpc_routes()
374
-                )
375
-            );
376
-            update_option(self::saved_routes_option_names, $routes, true);
377
-        }
378
-    }
379
-
380
-
381
-    /**
382
-     * Gets all the route information relating to EE models
383
-     *
384
-     * @return array @see get_ee_route_data
385
-     * @deprecated since version 4.9.1
386
-     */
387
-    protected function _register_model_routes(): array
388
-    {
389
-        $model_routes = [];
390
-        foreach (self::versions_served() as $version => $hidden_endpoint) {
391
-            $model_routes[ EED_Core_Rest_Api::ee_api_namespace
392
-                           . $version ] = $this->_get_config_route_data_for_version($version, $hidden_endpoint);
393
-        }
394
-        return $model_routes;
395
-    }
396
-
397
-
398
-    /**
399
-     * Decides whether or not to add write endpoints for this model.
400
-     *
401
-     * Currently, this defaults to exclude all global tables and models
402
-     * which would allow inserting WP core data (we don't want to duplicate
403
-     * what WP API does, as it's unnecessary, extra work, and potentially extra bugs)
404
-     *
405
-     * @param EEM_Base $model
406
-     * @return bool
407
-     */
408
-    public static function should_have_write_endpoints(EEM_Base $model): bool
409
-    {
410
-        if ($model->is_wp_core_model()) {
411
-            return false;
412
-        }
413
-        foreach ($model->get_tables() as $table) {
414
-            if ($table->is_global()) {
415
-                return false;
416
-            }
417
-        }
418
-        return true;
419
-    }
420
-
421
-
422
-    /**
423
-     * Gets the names of all models which should have plural routes (eg `ee/v4.8.36/events`)
424
-     * in this versioned namespace of EE4
425
-     *
426
-     * @param $version
427
-     * @return array keys are model names (eg 'Event') and values ar either classnames (eg 'EEM_Event')
428
-     */
429
-    public static function model_names_with_plural_routes($version): array
430
-    {
431
-        $model_version_info = new ModelVersionInfo($version);
432
-        $models_to_register = $model_version_info->modelsForRequestedVersion();
433
-        // let's not bother having endpoints for extra metas
434
-        unset(
435
-            $models_to_register['Extra_Meta'],
436
-            $models_to_register['Extra_Join'],
437
-            $models_to_register['Post_Meta']
438
-        );
439
-        return apply_filters(
440
-            'FHEE__EED_Core_REST_API___register_model_routes',
441
-            $models_to_register
442
-        );
443
-    }
444
-
445
-
446
-    /**
447
-     * Gets the route data for EE models in the specified version
448
-     *
449
-     * @param string  $version
450
-     * @param boolean $hidden_endpoint
451
-     * @return array
452
-     * @throws EE_Error
453
-     * @throws ReflectionException
454
-     */
455
-    protected function _get_model_route_data_for_version(string $version, bool $hidden_endpoint = false): array
456
-    {
457
-        $model_routes       = [];
458
-        $model_version_info = new ModelVersionInfo($version);
459
-        foreach (EED_Core_Rest_Api::model_names_with_plural_routes($version) as $model_name => $model_classname) {
460
-            $model = EE_Registry::instance()->load_model($model_name);
461
-            // if this isn't a valid model then let's skip iterate to the next item in the loop.
462
-            if (! $model instanceof EEM_Base) {
463
-                continue;
464
-            }
465
-            // yes we could just register one route for ALL models, but then they wouldn't show up in the index
466
-            $plural_model_route                    = EED_Core_Rest_Api::get_collection_route($model);
467
-            $singular_model_route                  = EED_Core_Rest_Api::get_entity_route($model, '(?P<id>[^\/]+)');
468
-            $model_routes[ $plural_model_route ]   = [
469
-                [
470
-                    'callback'        => [
471
-                        'EventEspresso\core\libraries\rest_api\controllers\model\Read',
472
-                        'handleRequestGetAll',
473
-                    ],
474
-                    'callback_args'   => [$version, $model_name],
475
-                    'methods'         => WP_REST_Server::READABLE,
476
-                    'hidden_endpoint' => $hidden_endpoint,
477
-                    'args'            => $this->_get_read_query_params($model, $version),
478
-                    '_links'          => [
479
-                        'self' => rest_url(EED_Core_Rest_Api::ee_api_namespace . $version . $singular_model_route),
480
-                    ],
481
-                ],
482
-                'schema' => [
483
-                    'schema_callback' => [
484
-                        'EventEspresso\core\libraries\rest_api\controllers\model\Read',
485
-                        'handleSchemaRequest',
486
-                    ],
487
-                    'callback_args'   => [$version, $model_name],
488
-                ],
489
-            ];
490
-            $model_routes[ $singular_model_route ] = [
491
-                [
492
-                    'callback'        => [
493
-                        'EventEspresso\core\libraries\rest_api\controllers\model\Read',
494
-                        'handleRequestGetOne',
495
-                    ],
496
-                    'callback_args'   => [$version, $model_name],
497
-                    'methods'         => WP_REST_Server::READABLE,
498
-                    'hidden_endpoint' => $hidden_endpoint,
499
-                    'args'            => $this->_get_response_selection_query_params($model, $version),
500
-                ],
501
-            ];
502
-            if (
503
-                apply_filters(
504
-                    'FHEE__EED_Core_Rest_Api___get_model_route_data_for_version__add_write_endpoints',
505
-                    EED_Core_Rest_Api::should_have_write_endpoints($model),
506
-                    $model
507
-                )
508
-            ) {
509
-                $model_routes[ $plural_model_route ][] = [
510
-                    'callback'        => [
511
-                        'EventEspresso\core\libraries\rest_api\controllers\model\Write',
512
-                        'handleRequestInsert',
513
-                    ],
514
-                    'callback_args'   => [$version, $model_name],
515
-                    'methods'         => WP_REST_Server::CREATABLE,
516
-                    'hidden_endpoint' => $hidden_endpoint,
517
-                    'args'            => $this->_get_write_params($model_name, $model_version_info, true),
518
-                ];
519
-                $model_routes[ $singular_model_route ] = array_merge(
520
-                    $model_routes[ $singular_model_route ],
521
-                    [
522
-                        [
523
-                            'callback'        => [
524
-                                'EventEspresso\core\libraries\rest_api\controllers\model\Write',
525
-                                'handleRequestUpdate',
526
-                            ],
527
-                            'callback_args'   => [$version, $model_name],
528
-                            'methods'         => WP_REST_Server::EDITABLE,
529
-                            'hidden_endpoint' => $hidden_endpoint,
530
-                            'args'            => $this->_get_write_params($model_name, $model_version_info),
531
-                        ],
532
-                        [
533
-                            'callback'        => [
534
-                                'EventEspresso\core\libraries\rest_api\controllers\model\Write',
535
-                                'handleRequestDelete',
536
-                            ],
537
-                            'callback_args'   => [$version, $model_name],
538
-                            'methods'         => WP_REST_Server::DELETABLE,
539
-                            'hidden_endpoint' => $hidden_endpoint,
540
-                            'args'            => $this->_get_delete_query_params($model, $version),
541
-                        ],
542
-                    ]
543
-                );
544
-            }
545
-            foreach ($model->relation_settings() as $relation_name => $relation_obj) {
546
-                $related_route                  = EED_Core_Rest_Api::get_relation_route_via(
547
-                    $model,
548
-                    '(?P<id>[^\/]+)',
549
-                    $relation_obj
550
-                );
551
-                $model_routes[ $related_route ] = [
552
-                    [
553
-                        'callback'        => [
554
-                            'EventEspresso\core\libraries\rest_api\controllers\model\Read',
555
-                            'handleRequestGetRelated',
556
-                        ],
557
-                        'callback_args'   => [$version, $model_name, $relation_name],
558
-                        'methods'         => WP_REST_Server::READABLE,
559
-                        'hidden_endpoint' => $hidden_endpoint,
560
-                        'args'            => $this->_get_read_query_params($relation_obj->get_other_model(), $version),
561
-                    ],
562
-                ];
563
-
564
-                $related_write_route                  = $related_route . '/' . '(?P<related_id>[^\/]+)';
565
-                $model_routes[ $related_write_route ] = [
566
-                    [
567
-                        'callback'        => [
568
-                            'EventEspresso\core\libraries\rest_api\controllers\model\Write',
569
-                            'handleRequestAddRelation',
570
-                        ],
571
-                        'callback_args'   => [$version, $model_name, $relation_name],
572
-                        'methods'         => WP_REST_Server::EDITABLE,
573
-                        'hidden_endpoint' => $hidden_endpoint,
574
-                        'args'            => $this->_get_add_relation_query_params(
575
-                            $model,
576
-                            $relation_obj->get_other_model(),
577
-                            $version
578
-                        ),
579
-                    ],
580
-                    [
581
-                        'callback'        => [
582
-                            'EventEspresso\core\libraries\rest_api\controllers\model\Write',
583
-                            'handleRequestRemoveRelation',
584
-                        ],
585
-                        'callback_args'   => [$version, $model_name, $relation_name],
586
-                        'methods'         => WP_REST_Server::DELETABLE,
587
-                        'hidden_endpoint' => $hidden_endpoint,
588
-                        'args'            => [],
589
-                    ],
590
-                ];
591
-            }
592
-        }
593
-        return $model_routes;
594
-    }
595
-
596
-
597
-    /**
598
-     * Gets the relative URI to a model's REST API plural route, after the EE4 versioned namespace,
599
-     * excluding the preceding slash.
600
-     * Eg you pass get_plural_route_to('Event') = 'events'
601
-     *
602
-     * @param EEM_Base $model
603
-     * @return string
604
-     */
605
-    public static function get_collection_route(EEM_Base $model): string
606
-    {
607
-        return EEH_Inflector::pluralize_and_lower($model->get_this_model_name());
608
-    }
609
-
610
-
611
-    /**
612
-     * Gets the relative URI to a model's REST API singular route, after the EE4 versioned namespace,
613
-     * excluding the preceding slash.
614
-     * Eg you pass get_plural_route_to('Event', 12) = 'events/12'
615
-     *
616
-     * @param EEM_Base   $model eg Event or Venue
617
-     * @param int|string $id
618
-     * @return string
619
-     */
620
-    public static function get_entity_route(EEM_Base $model, $id): string
621
-    {
622
-        return EED_Core_Rest_Api::get_collection_route($model) . '/' . $id;
623
-    }
624
-
625
-
626
-    /**
627
-     * Gets the relative URI to a model's REST API singular route, after the EE4 versioned namespace,
628
-     * excluding the preceding slash.
629
-     * Eg you pass get_plural_route_to('Event', 12) = 'events/12'
630
-     *
631
-     * @param EEM_Base               $model eg Event or Venue
632
-     * @param int|string             $id
633
-     * @param EE_Model_Relation_Base $relation_obj
634
-     * @return string
635
-     */
636
-    public static function get_relation_route_via(EEM_Base $model, $id, EE_Model_Relation_Base $relation_obj): string
637
-    {
638
-        $related_model_name_endpoint_part = ModelRead::getRelatedEntityName(
639
-            $relation_obj->get_other_model()->get_this_model_name(),
640
-            $relation_obj
641
-        );
642
-        return EED_Core_Rest_Api::get_entity_route($model, $id) . '/' . $related_model_name_endpoint_part;
643
-    }
644
-
645
-
646
-    /**
647
-     * Adds onto the $relative_route the EE4 REST API versioned namespace.
648
-     * Eg if given '4.8.36' and 'events', will return 'ee/v4.8.36/events'
649
-     *
650
-     * @param string $relative_route
651
-     * @param string $version
652
-     * @return string
653
-     */
654
-    public static function get_versioned_route_to(string $relative_route, string $version = '4.8.36'): string
655
-    {
656
-        return '/' . EED_Core_Rest_Api::ee_api_namespace . $version . '/' . $relative_route;
657
-    }
658
-
659
-
660
-    /**
661
-     * Adds all the RPC-style routes (remote procedure call-like routes, ie
662
-     * routes that don't conform to the traditional REST CRUD-style).
663
-     *
664
-     * @deprecated since 4.9.1
665
-     */
666
-    protected function _register_rpc_routes(): array
667
-    {
668
-        $routes = [];
669
-        foreach (self::versions_served() as $version => $hidden_endpoint) {
670
-            $routes[ self::ee_api_namespace . $version ] = $this->_get_rpc_route_data_for_version(
671
-                $version,
672
-                $hidden_endpoint
673
-            );
674
-        }
675
-        return $routes;
676
-    }
677
-
678
-
679
-    /**
680
-     * @param string  $version
681
-     * @param boolean $hidden_endpoint
682
-     * @return array
683
-     */
684
-    protected function _get_rpc_route_data_for_version(string $version, bool $hidden_endpoint = false): array
685
-    {
686
-        $this_versions_routes = [];
687
-        // checkin endpoint
688
-        $this_versions_routes['registrations/(?P<REG_ID>\d+)/toggle_checkin_for_datetime/(?P<DTT_ID>\d+)'] = [
689
-            [
690
-                'callback'        => [
691
-                    'EventEspresso\core\libraries\rest_api\controllers\rpc\Checkin',
692
-                    'handleRequestToggleCheckin',
693
-                ],
694
-                'methods'         => WP_REST_Server::CREATABLE,
695
-                'hidden_endpoint' => $hidden_endpoint,
696
-                'args'            => [
697
-                    'force' => [
698
-                        'required'    => false,
699
-                        'default'     => false,
700
-                        'description' => esc_html__(
701
-                        // @codingStandardsIgnoreStart
702
-                            'Whether to force toggle checkin, or to verify the registration status and allowed ticket uses',
703
-                            // @codingStandardsIgnoreEnd
704
-                            'event_espresso'
705
-                        ),
706
-                    ],
707
-                ],
708
-                'callback_args'   => [$version],
709
-            ],
710
-        ];
711
-        return apply_filters(
712
-            'FHEE__EED_Core_Rest_Api___register_rpc_routes__this_versions_routes',
713
-            $this_versions_routes,
714
-            $version,
715
-            $hidden_endpoint
716
-        );
717
-    }
718
-
719
-
720
-    /**
721
-     * Gets the query params that can be used when request one or many
722
-     *
723
-     * @param EEM_Base $model
724
-     * @param string   $version
725
-     * @return array
726
-     */
727
-    protected function _get_response_selection_query_params(EEM_Base $model, string $version): array
728
-    {
729
-        $query_params = [
730
-            'include'   => [
731
-                'required' => false,
732
-                'default'  => '*',
733
-                'type'     => 'string',
734
-            ],
735
-            'calculate' => [
736
-                'required'          => false,
737
-                'default'           => '',
738
-                'enum'              => self::$_field_calculator->retrieveCalculatedFieldsForModel($model),
739
-                'type'              => 'string',
740
-                // because we accept a CSV list of the enumerated strings, WP core validation and sanitization
741
-                // freaks out. We'll just validate this argument while handling the request
742
-                'validate_callback' => null,
743
-                'sanitize_callback' => null,
744
-            ],
745
-            'password'  => [
746
-                'required' => false,
747
-                'default'  => '',
748
-                'type'     => 'string',
749
-            ],
750
-        ];
751
-        return apply_filters(
752
-            'FHEE__EED_Core_Rest_Api___get_response_selection_query_params',
753
-            $query_params,
754
-            $model,
755
-            $version
756
-        );
757
-    }
758
-
759
-
760
-    /**
761
-     * Gets the parameters acceptable for delete requests
762
-     *
763
-     * @param EEM_Base $model
764
-     * @param string   $version
765
-     * @return array
766
-     */
767
-    protected function _get_delete_query_params(EEM_Base $model, string $version): array
768
-    {
769
-        $params_for_delete          = [
770
-            'allow_blocking' => [
771
-                'required' => false,
772
-                'default'  => true,
773
-                'type'     => 'boolean',
774
-            ],
775
-        ];
776
-        $params_for_delete['force'] = [
777
-            'required' => false,
778
-            'default'  => false,
779
-            'type'     => 'boolean',
780
-        ];
781
-        return apply_filters(
782
-            'FHEE__EED_Core_Rest_Api___get_delete_query_params',
783
-            $params_for_delete,
784
-            $model,
785
-            $version
786
-        );
787
-    }
788
-
789
-
790
-    /**
791
-     * @param EEM_Base $source_model
792
-     * @param EEM_Base $related_model
793
-     * @param string   $version
794
-     * @return array
795
-     * @throws EE_Error
796
-     */
797
-    protected function _get_add_relation_query_params(
798
-        EEM_Base $source_model,
799
-        EEM_Base $related_model,
800
-        string $version
801
-    ): array {
802
-        // if they're related through a HABTM relation, check for any non-FKs
803
-        $all_relation_settings = $source_model->relation_settings();
804
-        $relation_settings     = $all_relation_settings[ $related_model->get_this_model_name() ];
805
-        $params                = [];
806
-        if ($relation_settings instanceof EE_HABTM_Relation && $relation_settings->hasNonKeyFields()) {
807
-            foreach ($relation_settings->getNonKeyFields() as $field) {
808
-                /* @var $field EE_Model_Field_Base */
809
-                $params[ $field->get_name() ] = [
810
-                    'required'          => ! $field->is_nullable(),
811
-                    'default'           => ModelDataTranslator::prepareFieldValueForJson(
812
-                        $field,
813
-                        $field->get_default_value(),
814
-                        $version
815
-                    ),
816
-                    'type'              => $field->getSchemaType(),
817
-                    'validate_callback' => null,
818
-                    'sanitize_callback' => null,
819
-                ];
820
-            }
821
-        }
822
-        return $params;
823
-    }
824
-
825
-
826
-    /**
827
-     * Gets info about reading query params that are acceptable
828
-     *
829
-     * @param EEM_Base $model eg 'Event' or 'Venue'
830
-     * @param string   $version
831
-     * @return array    describing the args acceptable when querying this model
832
-     * @throws EE_Error
833
-     */
834
-    protected function _get_read_query_params(EEM_Base $model, string $version): array
835
-    {
836
-        $default_orderby = [];
837
-        foreach ($model->get_combined_primary_key_fields() as $key_field) {
838
-            $default_orderby[ $key_field->get_name() ] = 'ASC';
839
-        }
840
-        return array_merge(
841
-            $this->_get_response_selection_query_params($model, $version),
842
-            [
843
-                'where'    => [
844
-                    'required'          => false,
845
-                    'default'           => [],
846
-                    'type'              => 'object',
847
-                    // because we accept an almost infinite list of possible where conditions, WP
848
-                    // core validation and sanitization freaks out. We'll just validate this argument
849
-                    // while handling the request
850
-                    'validate_callback' => null,
851
-                    'sanitize_callback' => null,
852
-                ],
853
-                'limit'    => [
854
-                    'required'          => false,
855
-                    'default'           => EED_Core_Rest_Api::get_default_query_limit(),
856
-                    'type'              => [
857
-                        'array',
858
-                        'string',
859
-                        'integer',
860
-                    ],
861
-                    // because we accept a variety of types, WP core validation and sanitization
862
-                    // freaks out. We'll just validate this argument while handling the request
863
-                    'validate_callback' => null,
864
-                    'sanitize_callback' => null,
865
-                ],
866
-                'order_by' => [
867
-                    'required'          => false,
868
-                    'default'           => $default_orderby,
869
-                    'type'              => [
870
-                        'object',
871
-                        'string',
872
-                    ],// because we accept a variety of types, WP core validation and sanitization
873
-                    // freaks out. We'll just validate this argument while handling the request
874
-                    'validate_callback' => null,
875
-                    'sanitize_callback' => null,
876
-                ],
877
-                'group_by' => [
878
-                    'required'          => false,
879
-                    'default'           => null,
880
-                    'type'              => [
881
-                        'object',
882
-                        'string',
883
-                    ],
884
-                    // because we accept  an almost infinite list of possible groupings,
885
-                    // WP core validation and sanitization
886
-                    // freaks out. We'll just validate this argument while handling the request
887
-                    'validate_callback' => null,
888
-                    'sanitize_callback' => null,
889
-                ],
890
-                'having'   => [
891
-                    'required'          => false,
892
-                    'default'           => null,
893
-                    'type'              => 'object',
894
-                    // because we accept an almost infinite list of possible where conditions, WP
895
-                    // core validation and sanitization freaks out. We'll just validate this argument
896
-                    // while handling the request
897
-                    'validate_callback' => null,
898
-                    'sanitize_callback' => null,
899
-                ],
900
-                'caps'     => [
901
-                    'required' => false,
902
-                    'default'  => EEM_Base::caps_read,
903
-                    'type'     => 'string',
904
-                    'enum'     => [
905
-                        EEM_Base::caps_read,
906
-                        EEM_Base::caps_read_admin,
907
-                        EEM_Base::caps_edit,
908
-                        EEM_Base::caps_delete,
909
-                    ],
910
-                ],
911
-            ]
912
-        );
913
-    }
914
-
915
-
916
-    /**
917
-     * Gets parameter information for a model regarding writing data
918
-     *
919
-     * @param string           $model_name
920
-     * @param ModelVersionInfo $model_version_info
921
-     * @param boolean          $create                                       whether this is for request to create (in
922
-     *                                                                       which case we need all required params) or
923
-     *                                                                       just to update (in which case we don't
924
-     *                                                                       need those on every request)
925
-     * @return array
926
-     * @throws EE_Error
927
-     * @throws ReflectionException
928
-     */
929
-    protected function _get_write_params(
930
-        string $model_name,
931
-        ModelVersionInfo $model_version_info,
932
-        bool $create = false
933
-    ): array {
934
-        $model     = EE_Registry::instance()->load_model($model_name);
935
-        $fields    = $model_version_info->fieldsOnModelInThisVersion($model);
936
-        $args_info = [];
937
-        foreach ($fields as $field_name => $field_obj) {
938
-            if ($field_obj->is_auto_increment()) {
939
-                // totally ignore auto increment IDs
940
-                continue;
941
-            }
942
-            $arg_info             = $field_obj->getSchema();
943
-            $required             = $create && ! $field_obj->is_nullable() && $field_obj->get_default_value() === null;
944
-            $arg_info['required'] = $required;
945
-            // remove the read-only flag. If it were read-only we wouldn't list it as an argument while writing, right?
946
-            unset($arg_info['readonly']);
947
-            $schema_properties = $field_obj->getSchemaProperties();
948
-            if (
949
-                isset($schema_properties['raw'])
950
-                && $field_obj->getSchemaType() === 'object'
951
-            ) {
952
-                // if there's a "raw" form of this argument, use those properties instead
953
-                $arg_info = array_replace(
954
-                    $arg_info,
955
-                    $schema_properties['raw']
956
-                );
957
-            }
958
-            $arg_info['default'] = ModelDataTranslator::prepareFieldValueForJson(
959
-                $field_obj,
960
-                $field_obj->get_default_value(),
961
-                $model_version_info->requestedVersion()
962
-            );
963
-            // we do our own validation and sanitization within the controller
964
-            if (function_exists('rest_validate_value_from_schema')) {
965
-                $sanitize_callback = [
966
-                    'EED_Core_Rest_Api',
967
-                    'default_sanitize_callback',
968
-                ];
969
-            } else {
970
-                $sanitize_callback = null;
971
-            }
972
-            $arg_info['sanitize_callback'] = $sanitize_callback;
973
-            $args_info[ $field_name ]      = $arg_info;
974
-            if ($field_obj instanceof EE_Datetime_Field) {
975
-                $gmt_arg_info                      = $arg_info;
976
-                $gmt_arg_info['description']       = sprintf(
977
-                    esc_html__(
978
-                        '%1$s - the value for this field in UTC. Ignored if %2$s is provided.',
979
-                        'event_espresso'
980
-                    ),
981
-                    $field_obj->get_nicename(),
982
-                    $field_name
983
-                );
984
-                $args_info[ $field_name . '_gmt' ] = $gmt_arg_info;
985
-            }
986
-        }
987
-        return $args_info;
988
-    }
989
-
990
-
991
-    /**
992
-     * Replacement for WP API's 'rest_parse_request_arg'.
993
-     * If the value is blank but not required, don't bother validating it.
994
-     * Also, it uses our email validation instead of WP API's default.
995
-     *
996
-     * @param                 $value
997
-     * @param WP_REST_Request $request
998
-     * @param                 $param
999
-     * @return bool|true|WP_Error
1000
-     * @throws InvalidArgumentException
1001
-     * @throws InvalidInterfaceException
1002
-     * @throws InvalidDataTypeException
1003
-     */
1004
-    public static function default_sanitize_callback($value, WP_REST_Request $request, $param)
1005
-    {
1006
-        $attributes = $request->get_attributes();
1007
-        if (
1008
-            ! isset($attributes['args'][ $param ])
1009
-            || ! is_array($attributes['args'][ $param ])
1010
-        ) {
1011
-            $validation_result = true;
1012
-        } else {
1013
-            $args = $attributes['args'][ $param ];
1014
-            if (
1015
-                (
1016
-                    $value === ''
1017
-                    || $value === null
1018
-                )
1019
-                && (! isset($args['required'])
1020
-                    || $args['required'] === false
1021
-                )
1022
-            ) {
1023
-                // not required and not provided? that's cool
1024
-                $validation_result = true;
1025
-            } elseif (
1026
-                isset($args['format'])
1027
-                && $args['format'] === 'email'
1028
-            ) {
1029
-                $validation_result = true;
1030
-                if (! self::_validate_email($value)) {
1031
-                    $validation_result = new WP_Error(
1032
-                        'rest_invalid_param',
1033
-                        esc_html__(
1034
-                            'The email address is not valid or does not exist.',
1035
-                            'event_espresso'
1036
-                        )
1037
-                    );
1038
-                }
1039
-            } else {
1040
-                $validation_result = rest_validate_value_from_schema($value, $args, $param);
1041
-            }
1042
-        }
1043
-        if (is_wp_error($validation_result)) {
1044
-            return $validation_result;
1045
-        }
1046
-        return rest_sanitize_request_arg($value, $request, $param);
1047
-    }
1048
-
1049
-
1050
-    /**
1051
-     * Returns whether or not this email address is valid. Copied from EE_Email_Validation_Strategy::_validate_email()
1052
-     *
1053
-     * @param $email
1054
-     * @return bool
1055
-     * @throws InvalidArgumentException
1056
-     * @throws InvalidInterfaceException
1057
-     * @throws InvalidDataTypeException
1058
-     */
1059
-    protected static function _validate_email($email): bool
1060
-    {
1061
-        try {
1062
-            EmailAddressFactory::create($email);
1063
-            return true;
1064
-        } catch (EmailValidationException $e) {
1065
-            return false;
1066
-        }
1067
-    }
1068
-
1069
-
1070
-    /**
1071
-     * Gets routes for the config
1072
-     *
1073
-     * @return array @see _register_model_routes
1074
-     * @deprecated since version 4.9.1
1075
-     */
1076
-    protected function _register_config_routes(): array
1077
-    {
1078
-        $config_routes = [];
1079
-        foreach (self::versions_served() as $version => $hidden_endpoint) {
1080
-            $config_routes[ self::ee_api_namespace . $version ] = $this->_get_config_route_data_for_version(
1081
-                $version,
1082
-                $hidden_endpoint
1083
-            );
1084
-        }
1085
-        return $config_routes;
1086
-    }
1087
-
1088
-
1089
-    /**
1090
-     * Gets routes for the config for the specified version
1091
-     *
1092
-     * @param string  $version
1093
-     * @param boolean $hidden_endpoint
1094
-     * @return array
1095
-     */
1096
-    protected function _get_config_route_data_for_version(string $version, bool $hidden_endpoint): array
1097
-    {
1098
-        return [
1099
-            'config'    => [
1100
-                [
1101
-                    'callback'        => [
1102
-                        'EventEspresso\core\libraries\rest_api\controllers\config\Read',
1103
-                        'handleRequest',
1104
-                    ],
1105
-                    'methods'         => WP_REST_Server::READABLE,
1106
-                    'hidden_endpoint' => $hidden_endpoint,
1107
-                    'callback_args'   => [$version],
1108
-                ],
1109
-            ],
1110
-            'site_info' => [
1111
-                [
1112
-                    'callback'        => [
1113
-                        'EventEspresso\core\libraries\rest_api\controllers\config\Read',
1114
-                        'handleRequestSiteInfo',
1115
-                    ],
1116
-                    'methods'         => WP_REST_Server::READABLE,
1117
-                    'hidden_endpoint' => $hidden_endpoint,
1118
-                    'callback_args'   => [$version],
1119
-                ],
1120
-            ],
1121
-        ];
1122
-    }
1123
-
1124
-
1125
-    /**
1126
-     * Gets the meta info routes
1127
-     *
1128
-     * @return array @see _register_model_routes
1129
-     * @deprecated since version 4.9.1
1130
-     */
1131
-    protected function _register_meta_routes(): array
1132
-    {
1133
-        $meta_routes = [];
1134
-        foreach (self::versions_served() as $version => $hidden_endpoint) {
1135
-            $meta_routes[ self::ee_api_namespace . $version ] = $this->_get_meta_route_data_for_version(
1136
-                $version,
1137
-                $hidden_endpoint
1138
-            );
1139
-        }
1140
-        return $meta_routes;
1141
-    }
1142
-
1143
-
1144
-    /**
1145
-     * @param string  $version
1146
-     * @param boolean $hidden_endpoint
1147
-     * @return array
1148
-     */
1149
-    protected function _get_meta_route_data_for_version(string $version, bool $hidden_endpoint = false): array
1150
-    {
1151
-        return [
1152
-            'resources' => [
1153
-                [
1154
-                    'callback'        => [
1155
-                        'EventEspresso\core\libraries\rest_api\controllers\model\Meta',
1156
-                        'handleRequestModelsMeta',
1157
-                    ],
1158
-                    'methods'         => WP_REST_Server::READABLE,
1159
-                    'hidden_endpoint' => $hidden_endpoint,
1160
-                    'callback_args'   => [$version],
1161
-                ],
1162
-            ],
1163
-        ];
1164
-    }
1165
-
1166
-
1167
-    /**
1168
-     * Tries to hide old 4.6 endpoints from the
1169
-     *
1170
-     * @param array $route_data
1171
-     * @return array
1172
-     * @throws EE_Error
1173
-     * @throws ReflectionException
1174
-     */
1175
-    public static function hide_old_endpoints(array $route_data): array
1176
-    {
1177
-        // allow API clients to override which endpoints get hidden, in case
1178
-        // they want to discover particular endpoints
1179
-        // also, we don't have access to the request so we have to just grab it from the super global
1180
-        $force_show_ee_namespace = ltrim(
1181
-            EED_Core_Rest_Api::getRequest()->getRequestParam('force_show_ee_namespace'),
1182
-            '/'
1183
-        );
1184
-        foreach (EED_Core_Rest_Api::get_ee_route_data() as $namespace => $relative_urls) {
1185
-            foreach ($relative_urls as $resource_name => $endpoints) {
1186
-                foreach ($endpoints as $key => $endpoint) {
1187
-                    // skip schema and other route options
1188
-                    if (! is_numeric($key)) {
1189
-                        continue;
1190
-                    }
1191
-                    // by default, hide "hidden_endpoint"s, unless the request indicates
1192
-                    // to $force_show_ee_namespace, in which case only show that one
1193
-                    // namespace's endpoints (and hide all others)
1194
-                    if (
1195
-                        ($force_show_ee_namespace !== '' && $force_show_ee_namespace !== $namespace)
1196
-                        || ($endpoint['hidden_endpoint'] && $force_show_ee_namespace === '')
1197
-                    ) {
1198
-                        $full_route = '/' . ltrim($namespace, '/');
1199
-                        $full_route .= '/' . ltrim($resource_name, '/');
1200
-                        unset($route_data[ $full_route ]);
1201
-                    }
1202
-                }
1203
-            }
1204
-        }
1205
-        return $route_data;
1206
-    }
1207
-
1208
-
1209
-    /**
1210
-     * Returns an array describing which versions of core support serving requests for.
1211
-     * Keys are core versions' major and minor version, and values are the
1212
-     * LOWEST requested version they can serve. Eg, 4.7 can serve requests for 4.6-like
1213
-     * data by just removing a few models and fields from the responses. However, 4.15 might remove
1214
-     * the answers table entirely, in which case it would be very difficult for
1215
-     * it to serve 4.6-style responses.
1216
-     * Versions of core that are missing from this array are unknowns.
1217
-     * previous ver
1218
-     *
1219
-     * @return array
1220
-     */
1221
-    public static function version_compatibilities(): array
1222
-    {
1223
-        return apply_filters(
1224
-            'FHEE__EED_Core_REST_API__version_compatibilities',
1225
-            [
1226
-                '4.8.29' => '4.8.29',
1227
-                '4.8.33' => '4.8.29',
1228
-                '4.8.34' => '4.8.29',
1229
-                '4.8.36' => '4.8.29',
1230
-            ]
1231
-        );
1232
-    }
1233
-
1234
-
1235
-    /**
1236
-     * Gets the latest API version served. Eg if there
1237
-     * are two versions served of the API, 4.8.29 and 4.8.32, and
1238
-     * we are on core version 4.8.34, it will return the string "4.8.32"
1239
-     *
1240
-     * @return string
1241
-     */
1242
-    public static function latest_rest_api_version(): string
1243
-    {
1244
-        $versions_served      = EED_Core_Rest_Api::versions_served();
1245
-        $versions_served_keys = array_keys($versions_served);
1246
-        return end($versions_served_keys);
1247
-    }
1248
-
1249
-
1250
-    /**
1251
-     * Using EED_Core_Rest_Api::version_compatibilities(), determines what version of
1252
-     * EE the API can serve requests for. Eg, if we are on 4.15 of core, and
1253
-     * we can serve requests from 4.12 or later, this will return array( '4.12', '4.13', '4.14', '4.15' ).
1254
-     * We also indicate whether or not this version should be put in the index or not
1255
-     *
1256
-     * @return array keys are API version numbers (just major and minor numbers), and values
1257
-     * are whether or not they should be hidden
1258
-     */
1259
-    public static function versions_served(): array
1260
-    {
1261
-        $versions_served           = [];
1262
-        $possibly_served_versions  = EED_Core_Rest_Api::version_compatibilities();
1263
-        $lowest_compatible_version = end($possibly_served_versions);
1264
-        reset($possibly_served_versions);
1265
-        $versions_served_historically = array_keys($possibly_served_versions);
1266
-        $latest_version               = end($versions_served_historically);
1267
-        reset($versions_served_historically);
1268
-        // for each version of core we have ever served:
1269
-        foreach ($versions_served_historically as $key_versioned_endpoint) {
1270
-            // if it's not above the current core version, and it's compatible with the current version of core
1271
-
1272
-            if ($key_versioned_endpoint === $latest_version) {
1273
-                // don't hide the latest version in the index
1274
-                $versions_served[ $key_versioned_endpoint ] = false;
1275
-            } elseif (
1276
-                version_compare($key_versioned_endpoint, $lowest_compatible_version, '>=')
1277
-                && version_compare($key_versioned_endpoint, EED_Core_Rest_Api::core_version(), '<')
1278
-            ) {
1279
-                // include, but hide, previous versions which are still supported
1280
-                $versions_served[ $key_versioned_endpoint ] = true;
1281
-            } elseif (
1282
-                apply_filters(
1283
-                    'FHEE__EED_Core_Rest_Api__versions_served__include_incompatible_versions',
1284
-                    false,
1285
-                    $possibly_served_versions
1286
-                )
1287
-            ) {
1288
-                // if a version is no longer supported, don't include it in index or list of versions served
1289
-                $versions_served[ $key_versioned_endpoint ] = true;
1290
-            }
1291
-        }
1292
-        return $versions_served;
1293
-    }
1294
-
1295
-
1296
-    /**
1297
-     * Gets the major and minor version of EE core's version string
1298
-     *
1299
-     * @return string
1300
-     */
1301
-    public static function core_version(): string
1302
-    {
1303
-        return apply_filters(
1304
-            'FHEE__EED_Core_REST_API__core_version',
1305
-            implode(
1306
-                '.',
1307
-                array_slice(
1308
-                    explode(
1309
-                        '.',
1310
-                        espresso_version()
1311
-                    ),
1312
-                    0,
1313
-                    3
1314
-                )
1315
-            )
1316
-        );
1317
-    }
1318
-
1319
-
1320
-    /**
1321
-     * Gets the default limit that should be used when querying for resources
1322
-     *
1323
-     * @return int
1324
-     */
1325
-    public static function get_default_query_limit(): int
1326
-    {
1327
-        // we actually don't use a const because we want folks to always use
1328
-        // this method, not the const directly
1329
-        return apply_filters(
1330
-            'FHEE__EED_Core_Rest_Api__get_default_query_limit',
1331
-            50
1332
-        );
1333
-    }
1334
-
1335
-
1336
-    /**
1337
-     *
1338
-     * @param string $version api version string (i.e. '4.8.36')
1339
-     * @return array
1340
-     */
1341
-    public static function getCollectionRoutesIndexedByModelName(string $version = ''): array
1342
-    {
1343
-        $version           = empty($version) ? self::latest_rest_api_version() : $version;
1344
-        $model_names       = self::model_names_with_plural_routes($version);
1345
-        $collection_routes = [];
1346
-        foreach ($model_names as $model_name => $model_class_name) {
1347
-            $collection_routes[ strtolower($model_name) ] = '/' . self::ee_api_namespace . $version . '/'
1348
-                                                            . EEH_Inflector::pluralize_and_lower($model_name);
1349
-        }
1350
-        return $collection_routes;
1351
-    }
1352
-
1353
-
1354
-    /**
1355
-     * Returns an array of primary key names indexed by model names.
1356
-     *
1357
-     * @param string $version
1358
-     * @return array
1359
-     */
1360
-    public static function getPrimaryKeyNamesIndexedByModelName(string $version = ''): array
1361
-    {
1362
-        $version           = empty($version) ? self::latest_rest_api_version() : $version;
1363
-        $model_names       = self::model_names_with_plural_routes($version);
1364
-        $primary_key_items = [];
1365
-        foreach ($model_names as $model_name => $model_class_name) {
1366
-            $primary_keys = $model_class_name::instance()->get_combined_primary_key_fields();
1367
-            foreach ($primary_keys as $primary_key_name => $primary_key_field) {
1368
-                if (count($primary_keys) > 1) {
1369
-                    $primary_key_items[ strtolower($model_name) ][] = $primary_key_name;
1370
-                } else {
1371
-                    $primary_key_items[ strtolower($model_name) ] = $primary_key_name;
1372
-                }
1373
-            }
1374
-        }
1375
-        return $primary_key_items;
1376
-    }
1377
-
1378
-
1379
-    /**
1380
-     * Determines the EE REST API debug mode is activated, or not.
1381
-     *
1382
-     * @return bool
1383
-     * @since 4.9.76.p
1384
-     */
1385
-    public static function debugMode(): ?bool
1386
-    {
1387
-        static $debug_mode = null; // could be class prop
1388
-        if ($debug_mode === null) {
1389
-            $debug_mode = defined('EE_REST_API_DEBUG_MODE') && EE_REST_API_DEBUG_MODE;
1390
-        }
1391
-        return $debug_mode;
1392
-    }
1393
-
1394
-
1395
-    /**
1396
-     *    run - initial module setup
1397
-     *
1398
-     * @access    public
1399
-     * @param WP $WP
1400
-     * @return    void
1401
-     */
1402
-    public function run($WP)
1403
-    {
1404
-    }
24
+	const ee_api_namespace           = Domain::API_NAMESPACE;
25
+
26
+	const ee_api_namespace_for_regex = 'ee\/v([^/]*)\/';
27
+
28
+	const saved_routes_option_names  = 'ee_core_routes';
29
+
30
+	/**
31
+	 * string used in _links response bodies to make them globally unique.
32
+	 *
33
+	 * @see http://v2.wp-api.org/extending/linking/
34
+	 */
35
+	const ee_api_link_namespace = 'https://api.eventespresso.com/';
36
+
37
+	/**
38
+	 * @var CalculatedModelFields
39
+	 */
40
+	protected static $_field_calculator;
41
+
42
+
43
+	/**
44
+	 * @return EED_Core_Rest_Api|EED_Module
45
+	 * @throws EE_Error
46
+	 * @throws ReflectionException
47
+	 */
48
+	public static function instance()
49
+	{
50
+		self::$_field_calculator =
51
+			LoaderFactory::getLoader()->load('EventEspresso\core\libraries\rest_api\CalculatedModelFields');
52
+		return parent::get_instance(__CLASS__);
53
+	}
54
+
55
+
56
+	/**
57
+	 *    set_hooks - for hooking into EE Core, other modules, etc
58
+	 *
59
+	 * @access    public
60
+	 * @return    void
61
+	 */
62
+	public static function set_hooks()
63
+	{
64
+		self::set_hooks_both();
65
+	}
66
+
67
+
68
+	/**
69
+	 *    set_hooks_admin - for hooking into EE Admin Core, other modules, etc
70
+	 *
71
+	 * @access    public
72
+	 * @return    void
73
+	 */
74
+	public static function set_hooks_admin()
75
+	{
76
+		self::set_hooks_both();
77
+	}
78
+
79
+
80
+	public static function set_hooks_both()
81
+	{
82
+		add_action('rest_api_init', ['EED_Core_Rest_Api', 'set_hooks_rest_api'], 5);
83
+		add_action('rest_api_init', ['EED_Core_Rest_Api', 'register_routes'], 10);
84
+		add_filter('rest_route_data', ['EED_Core_Rest_Api', 'hide_old_endpoints'], 10, 2);
85
+		add_filter(
86
+			'rest_index',
87
+			['EventEspresso\core\libraries\rest_api\controllers\model\Meta', 'filterEeMetadataIntoIndex']
88
+		);
89
+		EED_Core_Rest_Api::invalidate_cached_route_data_on_version_change();
90
+	}
91
+
92
+
93
+	/**
94
+	 * sets up hooks which only need to be included as part of REST API requests;
95
+	 * other requests like to the frontend or admin etc don't need them
96
+	 *
97
+	 * @throws EE_Error
98
+	 */
99
+	public static function set_hooks_rest_api()
100
+	{
101
+		// set hooks which account for changes made to the API
102
+		EED_Core_Rest_Api::_set_hooks_for_changes();
103
+	}
104
+
105
+
106
+	/**
107
+	 * public wrapper of _set_hooks_for_changes.
108
+	 * Loads all the hooks which make requests to old versions of the API
109
+	 * appear the same as they always did
110
+	 *
111
+	 * @throws EE_Error
112
+	 */
113
+	public static function set_hooks_for_changes()
114
+	{
115
+		self::_set_hooks_for_changes();
116
+	}
117
+
118
+
119
+	/**
120
+	 * Loads all the hooks which make requests to old versions of the API
121
+	 * appear the same as they always did
122
+	 *
123
+	 * @throws EE_Error
124
+	 */
125
+	protected static function _set_hooks_for_changes()
126
+	{
127
+		$folder_contents = EEH_File::get_contents_of_folders([EE_LIBRARIES . 'rest_api/changes']);
128
+		foreach ($folder_contents as $classname_in_namespace => $filepath) {
129
+			// ignore the base parent class
130
+			// and legacy named classes
131
+			if (
132
+				$classname_in_namespace === 'ChangesInBase'
133
+				|| strpos($classname_in_namespace, 'Changes_In_') === 0
134
+			) {
135
+				continue;
136
+			}
137
+			$full_classname = 'EventEspresso\core\libraries\rest_api\changes\\' . $classname_in_namespace;
138
+			if (class_exists($full_classname)) {
139
+				$instance_of_class = new $full_classname();
140
+				if ($instance_of_class instanceof ChangesInBase) {
141
+					$instance_of_class->setHooks();
142
+				}
143
+			}
144
+		}
145
+	}
146
+
147
+
148
+	/**
149
+	 * Filters the WP routes to add our EE-related ones. This takes a bit of time
150
+	 * so we actually prefer to only do it when an EE plugin is activated or upgraded
151
+	 *
152
+	 * @throws EE_Error
153
+	 * @throws ReflectionException
154
+	 */
155
+	public static function register_routes()
156
+	{
157
+		foreach (EED_Core_Rest_Api::get_ee_route_data() as $namespace => $relative_routes) {
158
+			foreach ($relative_routes as $relative_route => $data_for_multiple_endpoints) {
159
+				/**
160
+				 * @var array     $data_for_multiple_endpoints numerically indexed array
161
+				 *                                         but can also contain route options like {
162
+				 * @type array    $schema                      {
163
+				 * @type callable $schema_callback
164
+				 * @type array    $callback_args               arguments that will be passed to the callback, after the
165
+				 * WP_REST_Request of course
166
+				 * }
167
+				 * }
168
+				 */
169
+				// when registering routes, register all the endpoints' data at the same time
170
+				$multiple_endpoint_args = [];
171
+				foreach ($data_for_multiple_endpoints as $endpoint_key => $data_for_single_endpoint) {
172
+					/**
173
+					 * @var array     $data_for_single_endpoint {
174
+					 * @type callable $callback
175
+					 * @type string methods
176
+					 * @type array args
177
+					 * @type array _links
178
+					 * @type array    $callback_args            arguments that will be passed to the callback, after the
179
+					 * WP_REST_Request of course
180
+					 * }
181
+					 */
182
+					// skip route options
183
+					if (! is_numeric($endpoint_key)) {
184
+						continue;
185
+					}
186
+					if (! isset($data_for_single_endpoint['callback'], $data_for_single_endpoint['methods'])) {
187
+						throw new EE_Error(
188
+							esc_html__(
189
+							// @codingStandardsIgnoreStart
190
+								'Endpoint configuration data needs to have entries "callback" (callable) and "methods" (comma-separated list of accepts HTTP methods).',
191
+								// @codingStandardsIgnoreEnd
192
+								'event_espresso'
193
+							)
194
+						);
195
+					}
196
+					$callback             = $data_for_single_endpoint['callback'];
197
+					$single_endpoint_args = [
198
+						'methods' => $data_for_single_endpoint['methods'],
199
+						'args'    => $data_for_single_endpoint['args'] ?? [],
200
+					];
201
+					if (isset($data_for_single_endpoint['_links'])) {
202
+						$single_endpoint_args['_links'] = $data_for_single_endpoint['_links'];
203
+					}
204
+					if (isset($data_for_single_endpoint['callback_args'])) {
205
+						$callback_args                    = $data_for_single_endpoint['callback_args'];
206
+						$single_endpoint_args['callback'] = function (WP_REST_Request $request) use (
207
+							$callback,
208
+							$callback_args
209
+						) {
210
+							array_unshift($callback_args, $request);
211
+							return call_user_func_array(
212
+								$callback,
213
+								$callback_args
214
+							);
215
+						};
216
+					} else {
217
+						$single_endpoint_args['callback'] = $data_for_single_endpoint['callback'];
218
+					}
219
+					// As of WordPress 5.5, if a permission_callback is not provided,
220
+					// the REST API will issue a _doing_it_wrong notice.
221
+					// Since the EE REST API defers capabilities to the db model system,
222
+					// we will just use the generic WP callback for public endpoints
223
+					if (! isset($single_endpoint_args['permission_callback'])) {
224
+						$single_endpoint_args['permission_callback'] = '__return_true';
225
+					}
226
+					$multiple_endpoint_args[] = $single_endpoint_args;
227
+				}
228
+				if (isset($data_for_multiple_endpoints['schema'])) {
229
+					$schema_route_data                = $data_for_multiple_endpoints['schema'];
230
+					$schema_callback                  = $schema_route_data['schema_callback'];
231
+					$callback_args                    = $schema_route_data['callback_args'];
232
+					$multiple_endpoint_args['schema'] = function () use ($schema_callback, $callback_args) {
233
+						return call_user_func_array(
234
+							$schema_callback,
235
+							$callback_args
236
+						);
237
+					};
238
+				}
239
+				register_rest_route(
240
+					$namespace,
241
+					$relative_route,
242
+					$multiple_endpoint_args
243
+				);
244
+			}
245
+		}
246
+	}
247
+
248
+
249
+	/**
250
+	 * Checks if there was a version change or something that merits invalidating the cached
251
+	 * route data. If so, invalidates the cached route data so that it gets refreshed
252
+	 * next time the WP API is used
253
+	 */
254
+	public static function invalidate_cached_route_data_on_version_change()
255
+	{
256
+		if (EE_System::instance()->detect_req_type() !== EE_System::req_type_normal) {
257
+			EED_Core_Rest_Api::invalidate_cached_route_data();
258
+		}
259
+		foreach (EE_Registry::instance()->addons as $addon) {
260
+			if ($addon instanceof EE_Addon && $addon->detect_req_type() !== EE_System::req_type_normal) {
261
+				EED_Core_Rest_Api::invalidate_cached_route_data();
262
+			}
263
+		}
264
+	}
265
+
266
+
267
+	/**
268
+	 * Removes the cached route data so it will get refreshed next time the WP API is used
269
+	 */
270
+	public static function invalidate_cached_route_data()
271
+	{
272
+		// delete the saved EE REST API routes
273
+		foreach (EED_Core_Rest_Api::versions_served() as $version => $hidden) {
274
+			delete_option(EED_Core_Rest_Api::saved_routes_option_names . $version);
275
+		}
276
+	}
277
+
278
+
279
+	/**
280
+	 * Gets the EE route data
281
+	 *
282
+	 * @return array top-level key is the namespace, next-level key is the route and its value is array{
283
+	 * @throws EE_Error
284
+	 * @throws ReflectionException
285
+	 * @type string|array $callback
286
+	 * @type string       $methods
287
+	 * @type boolean      $hidden_endpoint
288
+	 * }
289
+	 */
290
+	public static function get_ee_route_data(): array
291
+	{
292
+		$ee_routes = [];
293
+		foreach (self::versions_served() as $version => $hidden_endpoints) {
294
+			$ee_routes[ self::ee_api_namespace . $version ] = self::_get_ee_route_data_for_version(
295
+				$version,
296
+				$hidden_endpoints
297
+			);
298
+		}
299
+		return $ee_routes;
300
+	}
301
+
302
+
303
+	/**
304
+	 * Gets the EE route data from the wp options if it exists already,
305
+	 * otherwise re-generates it and saves it to the option
306
+	 *
307
+	 * @param string  $version
308
+	 * @param boolean $hidden_endpoints
309
+	 * @return array
310
+	 * @throws EE_Error
311
+	 * @throws ReflectionException
312
+	 */
313
+	protected static function _get_ee_route_data_for_version(string $version, bool $hidden_endpoints = false): array
314
+	{
315
+		$ee_routes = get_option(self::saved_routes_option_names . $version, null);
316
+		if (! $ee_routes || EED_Core_Rest_Api::debugMode()) {
317
+			$ee_routes = self::_save_ee_route_data_for_version($version, $hidden_endpoints);
318
+		}
319
+		return $ee_routes;
320
+	}
321
+
322
+
323
+	/**
324
+	 * Saves the EE REST API route data to a wp option and returns it
325
+	 *
326
+	 * @param string  $version
327
+	 * @param boolean $hidden_endpoints
328
+	 * @return mixed|null
329
+	 * @throws EE_Error
330
+	 * @throws ReflectionException
331
+	 */
332
+	protected static function _save_ee_route_data_for_version(string $version, bool $hidden_endpoints = false)
333
+	{
334
+		$instance    = self::instance();
335
+		$routes      = apply_filters(
336
+			'EED_Core_Rest_Api__save_ee_route_data_for_version__routes',
337
+			array_replace_recursive(
338
+				$instance->_get_config_route_data_for_version($version, $hidden_endpoints),
339
+				$instance->_get_meta_route_data_for_version($version, $hidden_endpoints),
340
+				$instance->_get_model_route_data_for_version($version, $hidden_endpoints),
341
+				$instance->_get_rpc_route_data_for_version($version, $hidden_endpoints)
342
+			)
343
+		);
344
+		$option_name = self::saved_routes_option_names . $version;
345
+		if (get_option($option_name)) {
346
+			update_option($option_name, $routes, true);
347
+		} else {
348
+			add_option($option_name, $routes, null, 'no');
349
+		}
350
+		return $routes;
351
+	}
352
+
353
+
354
+	/**
355
+	 * Calculates all the EE routes and saves it to a WordPress option so we don't
356
+	 * need to calculate it on every request
357
+	 *
358
+	 * @return void
359
+	 * @throws EE_Error
360
+	 * @throws ReflectionException
361
+	 * @deprecated since version 4.9.1
362
+	 */
363
+	public static function save_ee_routes()
364
+	{
365
+		if (EE_Maintenance_Mode::instance()->models_can_query()) {
366
+			$instance = self::instance();
367
+			$routes   = apply_filters(
368
+				'EED_Core_Rest_Api__save_ee_routes__routes',
369
+				array_replace_recursive(
370
+					$instance->_register_config_routes(),
371
+					$instance->_register_meta_routes(),
372
+					$instance->_register_model_routes(),
373
+					$instance->_register_rpc_routes()
374
+				)
375
+			);
376
+			update_option(self::saved_routes_option_names, $routes, true);
377
+		}
378
+	}
379
+
380
+
381
+	/**
382
+	 * Gets all the route information relating to EE models
383
+	 *
384
+	 * @return array @see get_ee_route_data
385
+	 * @deprecated since version 4.9.1
386
+	 */
387
+	protected function _register_model_routes(): array
388
+	{
389
+		$model_routes = [];
390
+		foreach (self::versions_served() as $version => $hidden_endpoint) {
391
+			$model_routes[ EED_Core_Rest_Api::ee_api_namespace
392
+						   . $version ] = $this->_get_config_route_data_for_version($version, $hidden_endpoint);
393
+		}
394
+		return $model_routes;
395
+	}
396
+
397
+
398
+	/**
399
+	 * Decides whether or not to add write endpoints for this model.
400
+	 *
401
+	 * Currently, this defaults to exclude all global tables and models
402
+	 * which would allow inserting WP core data (we don't want to duplicate
403
+	 * what WP API does, as it's unnecessary, extra work, and potentially extra bugs)
404
+	 *
405
+	 * @param EEM_Base $model
406
+	 * @return bool
407
+	 */
408
+	public static function should_have_write_endpoints(EEM_Base $model): bool
409
+	{
410
+		if ($model->is_wp_core_model()) {
411
+			return false;
412
+		}
413
+		foreach ($model->get_tables() as $table) {
414
+			if ($table->is_global()) {
415
+				return false;
416
+			}
417
+		}
418
+		return true;
419
+	}
420
+
421
+
422
+	/**
423
+	 * Gets the names of all models which should have plural routes (eg `ee/v4.8.36/events`)
424
+	 * in this versioned namespace of EE4
425
+	 *
426
+	 * @param $version
427
+	 * @return array keys are model names (eg 'Event') and values ar either classnames (eg 'EEM_Event')
428
+	 */
429
+	public static function model_names_with_plural_routes($version): array
430
+	{
431
+		$model_version_info = new ModelVersionInfo($version);
432
+		$models_to_register = $model_version_info->modelsForRequestedVersion();
433
+		// let's not bother having endpoints for extra metas
434
+		unset(
435
+			$models_to_register['Extra_Meta'],
436
+			$models_to_register['Extra_Join'],
437
+			$models_to_register['Post_Meta']
438
+		);
439
+		return apply_filters(
440
+			'FHEE__EED_Core_REST_API___register_model_routes',
441
+			$models_to_register
442
+		);
443
+	}
444
+
445
+
446
+	/**
447
+	 * Gets the route data for EE models in the specified version
448
+	 *
449
+	 * @param string  $version
450
+	 * @param boolean $hidden_endpoint
451
+	 * @return array
452
+	 * @throws EE_Error
453
+	 * @throws ReflectionException
454
+	 */
455
+	protected function _get_model_route_data_for_version(string $version, bool $hidden_endpoint = false): array
456
+	{
457
+		$model_routes       = [];
458
+		$model_version_info = new ModelVersionInfo($version);
459
+		foreach (EED_Core_Rest_Api::model_names_with_plural_routes($version) as $model_name => $model_classname) {
460
+			$model = EE_Registry::instance()->load_model($model_name);
461
+			// if this isn't a valid model then let's skip iterate to the next item in the loop.
462
+			if (! $model instanceof EEM_Base) {
463
+				continue;
464
+			}
465
+			// yes we could just register one route for ALL models, but then they wouldn't show up in the index
466
+			$plural_model_route                    = EED_Core_Rest_Api::get_collection_route($model);
467
+			$singular_model_route                  = EED_Core_Rest_Api::get_entity_route($model, '(?P<id>[^\/]+)');
468
+			$model_routes[ $plural_model_route ]   = [
469
+				[
470
+					'callback'        => [
471
+						'EventEspresso\core\libraries\rest_api\controllers\model\Read',
472
+						'handleRequestGetAll',
473
+					],
474
+					'callback_args'   => [$version, $model_name],
475
+					'methods'         => WP_REST_Server::READABLE,
476
+					'hidden_endpoint' => $hidden_endpoint,
477
+					'args'            => $this->_get_read_query_params($model, $version),
478
+					'_links'          => [
479
+						'self' => rest_url(EED_Core_Rest_Api::ee_api_namespace . $version . $singular_model_route),
480
+					],
481
+				],
482
+				'schema' => [
483
+					'schema_callback' => [
484
+						'EventEspresso\core\libraries\rest_api\controllers\model\Read',
485
+						'handleSchemaRequest',
486
+					],
487
+					'callback_args'   => [$version, $model_name],
488
+				],
489
+			];
490
+			$model_routes[ $singular_model_route ] = [
491
+				[
492
+					'callback'        => [
493
+						'EventEspresso\core\libraries\rest_api\controllers\model\Read',
494
+						'handleRequestGetOne',
495
+					],
496
+					'callback_args'   => [$version, $model_name],
497
+					'methods'         => WP_REST_Server::READABLE,
498
+					'hidden_endpoint' => $hidden_endpoint,
499
+					'args'            => $this->_get_response_selection_query_params($model, $version),
500
+				],
501
+			];
502
+			if (
503
+				apply_filters(
504
+					'FHEE__EED_Core_Rest_Api___get_model_route_data_for_version__add_write_endpoints',
505
+					EED_Core_Rest_Api::should_have_write_endpoints($model),
506
+					$model
507
+				)
508
+			) {
509
+				$model_routes[ $plural_model_route ][] = [
510
+					'callback'        => [
511
+						'EventEspresso\core\libraries\rest_api\controllers\model\Write',
512
+						'handleRequestInsert',
513
+					],
514
+					'callback_args'   => [$version, $model_name],
515
+					'methods'         => WP_REST_Server::CREATABLE,
516
+					'hidden_endpoint' => $hidden_endpoint,
517
+					'args'            => $this->_get_write_params($model_name, $model_version_info, true),
518
+				];
519
+				$model_routes[ $singular_model_route ] = array_merge(
520
+					$model_routes[ $singular_model_route ],
521
+					[
522
+						[
523
+							'callback'        => [
524
+								'EventEspresso\core\libraries\rest_api\controllers\model\Write',
525
+								'handleRequestUpdate',
526
+							],
527
+							'callback_args'   => [$version, $model_name],
528
+							'methods'         => WP_REST_Server::EDITABLE,
529
+							'hidden_endpoint' => $hidden_endpoint,
530
+							'args'            => $this->_get_write_params($model_name, $model_version_info),
531
+						],
532
+						[
533
+							'callback'        => [
534
+								'EventEspresso\core\libraries\rest_api\controllers\model\Write',
535
+								'handleRequestDelete',
536
+							],
537
+							'callback_args'   => [$version, $model_name],
538
+							'methods'         => WP_REST_Server::DELETABLE,
539
+							'hidden_endpoint' => $hidden_endpoint,
540
+							'args'            => $this->_get_delete_query_params($model, $version),
541
+						],
542
+					]
543
+				);
544
+			}
545
+			foreach ($model->relation_settings() as $relation_name => $relation_obj) {
546
+				$related_route                  = EED_Core_Rest_Api::get_relation_route_via(
547
+					$model,
548
+					'(?P<id>[^\/]+)',
549
+					$relation_obj
550
+				);
551
+				$model_routes[ $related_route ] = [
552
+					[
553
+						'callback'        => [
554
+							'EventEspresso\core\libraries\rest_api\controllers\model\Read',
555
+							'handleRequestGetRelated',
556
+						],
557
+						'callback_args'   => [$version, $model_name, $relation_name],
558
+						'methods'         => WP_REST_Server::READABLE,
559
+						'hidden_endpoint' => $hidden_endpoint,
560
+						'args'            => $this->_get_read_query_params($relation_obj->get_other_model(), $version),
561
+					],
562
+				];
563
+
564
+				$related_write_route                  = $related_route . '/' . '(?P<related_id>[^\/]+)';
565
+				$model_routes[ $related_write_route ] = [
566
+					[
567
+						'callback'        => [
568
+							'EventEspresso\core\libraries\rest_api\controllers\model\Write',
569
+							'handleRequestAddRelation',
570
+						],
571
+						'callback_args'   => [$version, $model_name, $relation_name],
572
+						'methods'         => WP_REST_Server::EDITABLE,
573
+						'hidden_endpoint' => $hidden_endpoint,
574
+						'args'            => $this->_get_add_relation_query_params(
575
+							$model,
576
+							$relation_obj->get_other_model(),
577
+							$version
578
+						),
579
+					],
580
+					[
581
+						'callback'        => [
582
+							'EventEspresso\core\libraries\rest_api\controllers\model\Write',
583
+							'handleRequestRemoveRelation',
584
+						],
585
+						'callback_args'   => [$version, $model_name, $relation_name],
586
+						'methods'         => WP_REST_Server::DELETABLE,
587
+						'hidden_endpoint' => $hidden_endpoint,
588
+						'args'            => [],
589
+					],
590
+				];
591
+			}
592
+		}
593
+		return $model_routes;
594
+	}
595
+
596
+
597
+	/**
598
+	 * Gets the relative URI to a model's REST API plural route, after the EE4 versioned namespace,
599
+	 * excluding the preceding slash.
600
+	 * Eg you pass get_plural_route_to('Event') = 'events'
601
+	 *
602
+	 * @param EEM_Base $model
603
+	 * @return string
604
+	 */
605
+	public static function get_collection_route(EEM_Base $model): string
606
+	{
607
+		return EEH_Inflector::pluralize_and_lower($model->get_this_model_name());
608
+	}
609
+
610
+
611
+	/**
612
+	 * Gets the relative URI to a model's REST API singular route, after the EE4 versioned namespace,
613
+	 * excluding the preceding slash.
614
+	 * Eg you pass get_plural_route_to('Event', 12) = 'events/12'
615
+	 *
616
+	 * @param EEM_Base   $model eg Event or Venue
617
+	 * @param int|string $id
618
+	 * @return string
619
+	 */
620
+	public static function get_entity_route(EEM_Base $model, $id): string
621
+	{
622
+		return EED_Core_Rest_Api::get_collection_route($model) . '/' . $id;
623
+	}
624
+
625
+
626
+	/**
627
+	 * Gets the relative URI to a model's REST API singular route, after the EE4 versioned namespace,
628
+	 * excluding the preceding slash.
629
+	 * Eg you pass get_plural_route_to('Event', 12) = 'events/12'
630
+	 *
631
+	 * @param EEM_Base               $model eg Event or Venue
632
+	 * @param int|string             $id
633
+	 * @param EE_Model_Relation_Base $relation_obj
634
+	 * @return string
635
+	 */
636
+	public static function get_relation_route_via(EEM_Base $model, $id, EE_Model_Relation_Base $relation_obj): string
637
+	{
638
+		$related_model_name_endpoint_part = ModelRead::getRelatedEntityName(
639
+			$relation_obj->get_other_model()->get_this_model_name(),
640
+			$relation_obj
641
+		);
642
+		return EED_Core_Rest_Api::get_entity_route($model, $id) . '/' . $related_model_name_endpoint_part;
643
+	}
644
+
645
+
646
+	/**
647
+	 * Adds onto the $relative_route the EE4 REST API versioned namespace.
648
+	 * Eg if given '4.8.36' and 'events', will return 'ee/v4.8.36/events'
649
+	 *
650
+	 * @param string $relative_route
651
+	 * @param string $version
652
+	 * @return string
653
+	 */
654
+	public static function get_versioned_route_to(string $relative_route, string $version = '4.8.36'): string
655
+	{
656
+		return '/' . EED_Core_Rest_Api::ee_api_namespace . $version . '/' . $relative_route;
657
+	}
658
+
659
+
660
+	/**
661
+	 * Adds all the RPC-style routes (remote procedure call-like routes, ie
662
+	 * routes that don't conform to the traditional REST CRUD-style).
663
+	 *
664
+	 * @deprecated since 4.9.1
665
+	 */
666
+	protected function _register_rpc_routes(): array
667
+	{
668
+		$routes = [];
669
+		foreach (self::versions_served() as $version => $hidden_endpoint) {
670
+			$routes[ self::ee_api_namespace . $version ] = $this->_get_rpc_route_data_for_version(
671
+				$version,
672
+				$hidden_endpoint
673
+			);
674
+		}
675
+		return $routes;
676
+	}
677
+
678
+
679
+	/**
680
+	 * @param string  $version
681
+	 * @param boolean $hidden_endpoint
682
+	 * @return array
683
+	 */
684
+	protected function _get_rpc_route_data_for_version(string $version, bool $hidden_endpoint = false): array
685
+	{
686
+		$this_versions_routes = [];
687
+		// checkin endpoint
688
+		$this_versions_routes['registrations/(?P<REG_ID>\d+)/toggle_checkin_for_datetime/(?P<DTT_ID>\d+)'] = [
689
+			[
690
+				'callback'        => [
691
+					'EventEspresso\core\libraries\rest_api\controllers\rpc\Checkin',
692
+					'handleRequestToggleCheckin',
693
+				],
694
+				'methods'         => WP_REST_Server::CREATABLE,
695
+				'hidden_endpoint' => $hidden_endpoint,
696
+				'args'            => [
697
+					'force' => [
698
+						'required'    => false,
699
+						'default'     => false,
700
+						'description' => esc_html__(
701
+						// @codingStandardsIgnoreStart
702
+							'Whether to force toggle checkin, or to verify the registration status and allowed ticket uses',
703
+							// @codingStandardsIgnoreEnd
704
+							'event_espresso'
705
+						),
706
+					],
707
+				],
708
+				'callback_args'   => [$version],
709
+			],
710
+		];
711
+		return apply_filters(
712
+			'FHEE__EED_Core_Rest_Api___register_rpc_routes__this_versions_routes',
713
+			$this_versions_routes,
714
+			$version,
715
+			$hidden_endpoint
716
+		);
717
+	}
718
+
719
+
720
+	/**
721
+	 * Gets the query params that can be used when request one or many
722
+	 *
723
+	 * @param EEM_Base $model
724
+	 * @param string   $version
725
+	 * @return array
726
+	 */
727
+	protected function _get_response_selection_query_params(EEM_Base $model, string $version): array
728
+	{
729
+		$query_params = [
730
+			'include'   => [
731
+				'required' => false,
732
+				'default'  => '*',
733
+				'type'     => 'string',
734
+			],
735
+			'calculate' => [
736
+				'required'          => false,
737
+				'default'           => '',
738
+				'enum'              => self::$_field_calculator->retrieveCalculatedFieldsForModel($model),
739
+				'type'              => 'string',
740
+				// because we accept a CSV list of the enumerated strings, WP core validation and sanitization
741
+				// freaks out. We'll just validate this argument while handling the request
742
+				'validate_callback' => null,
743
+				'sanitize_callback' => null,
744
+			],
745
+			'password'  => [
746
+				'required' => false,
747
+				'default'  => '',
748
+				'type'     => 'string',
749
+			],
750
+		];
751
+		return apply_filters(
752
+			'FHEE__EED_Core_Rest_Api___get_response_selection_query_params',
753
+			$query_params,
754
+			$model,
755
+			$version
756
+		);
757
+	}
758
+
759
+
760
+	/**
761
+	 * Gets the parameters acceptable for delete requests
762
+	 *
763
+	 * @param EEM_Base $model
764
+	 * @param string   $version
765
+	 * @return array
766
+	 */
767
+	protected function _get_delete_query_params(EEM_Base $model, string $version): array
768
+	{
769
+		$params_for_delete          = [
770
+			'allow_blocking' => [
771
+				'required' => false,
772
+				'default'  => true,
773
+				'type'     => 'boolean',
774
+			],
775
+		];
776
+		$params_for_delete['force'] = [
777
+			'required' => false,
778
+			'default'  => false,
779
+			'type'     => 'boolean',
780
+		];
781
+		return apply_filters(
782
+			'FHEE__EED_Core_Rest_Api___get_delete_query_params',
783
+			$params_for_delete,
784
+			$model,
785
+			$version
786
+		);
787
+	}
788
+
789
+
790
+	/**
791
+	 * @param EEM_Base $source_model
792
+	 * @param EEM_Base $related_model
793
+	 * @param string   $version
794
+	 * @return array
795
+	 * @throws EE_Error
796
+	 */
797
+	protected function _get_add_relation_query_params(
798
+		EEM_Base $source_model,
799
+		EEM_Base $related_model,
800
+		string $version
801
+	): array {
802
+		// if they're related through a HABTM relation, check for any non-FKs
803
+		$all_relation_settings = $source_model->relation_settings();
804
+		$relation_settings     = $all_relation_settings[ $related_model->get_this_model_name() ];
805
+		$params                = [];
806
+		if ($relation_settings instanceof EE_HABTM_Relation && $relation_settings->hasNonKeyFields()) {
807
+			foreach ($relation_settings->getNonKeyFields() as $field) {
808
+				/* @var $field EE_Model_Field_Base */
809
+				$params[ $field->get_name() ] = [
810
+					'required'          => ! $field->is_nullable(),
811
+					'default'           => ModelDataTranslator::prepareFieldValueForJson(
812
+						$field,
813
+						$field->get_default_value(),
814
+						$version
815
+					),
816
+					'type'              => $field->getSchemaType(),
817
+					'validate_callback' => null,
818
+					'sanitize_callback' => null,
819
+				];
820
+			}
821
+		}
822
+		return $params;
823
+	}
824
+
825
+
826
+	/**
827
+	 * Gets info about reading query params that are acceptable
828
+	 *
829
+	 * @param EEM_Base $model eg 'Event' or 'Venue'
830
+	 * @param string   $version
831
+	 * @return array    describing the args acceptable when querying this model
832
+	 * @throws EE_Error
833
+	 */
834
+	protected function _get_read_query_params(EEM_Base $model, string $version): array
835
+	{
836
+		$default_orderby = [];
837
+		foreach ($model->get_combined_primary_key_fields() as $key_field) {
838
+			$default_orderby[ $key_field->get_name() ] = 'ASC';
839
+		}
840
+		return array_merge(
841
+			$this->_get_response_selection_query_params($model, $version),
842
+			[
843
+				'where'    => [
844
+					'required'          => false,
845
+					'default'           => [],
846
+					'type'              => 'object',
847
+					// because we accept an almost infinite list of possible where conditions, WP
848
+					// core validation and sanitization freaks out. We'll just validate this argument
849
+					// while handling the request
850
+					'validate_callback' => null,
851
+					'sanitize_callback' => null,
852
+				],
853
+				'limit'    => [
854
+					'required'          => false,
855
+					'default'           => EED_Core_Rest_Api::get_default_query_limit(),
856
+					'type'              => [
857
+						'array',
858
+						'string',
859
+						'integer',
860
+					],
861
+					// because we accept a variety of types, WP core validation and sanitization
862
+					// freaks out. We'll just validate this argument while handling the request
863
+					'validate_callback' => null,
864
+					'sanitize_callback' => null,
865
+				],
866
+				'order_by' => [
867
+					'required'          => false,
868
+					'default'           => $default_orderby,
869
+					'type'              => [
870
+						'object',
871
+						'string',
872
+					],// because we accept a variety of types, WP core validation and sanitization
873
+					// freaks out. We'll just validate this argument while handling the request
874
+					'validate_callback' => null,
875
+					'sanitize_callback' => null,
876
+				],
877
+				'group_by' => [
878
+					'required'          => false,
879
+					'default'           => null,
880
+					'type'              => [
881
+						'object',
882
+						'string',
883
+					],
884
+					// because we accept  an almost infinite list of possible groupings,
885
+					// WP core validation and sanitization
886
+					// freaks out. We'll just validate this argument while handling the request
887
+					'validate_callback' => null,
888
+					'sanitize_callback' => null,
889
+				],
890
+				'having'   => [
891
+					'required'          => false,
892
+					'default'           => null,
893
+					'type'              => 'object',
894
+					// because we accept an almost infinite list of possible where conditions, WP
895
+					// core validation and sanitization freaks out. We'll just validate this argument
896
+					// while handling the request
897
+					'validate_callback' => null,
898
+					'sanitize_callback' => null,
899
+				],
900
+				'caps'     => [
901
+					'required' => false,
902
+					'default'  => EEM_Base::caps_read,
903
+					'type'     => 'string',
904
+					'enum'     => [
905
+						EEM_Base::caps_read,
906
+						EEM_Base::caps_read_admin,
907
+						EEM_Base::caps_edit,
908
+						EEM_Base::caps_delete,
909
+					],
910
+				],
911
+			]
912
+		);
913
+	}
914
+
915
+
916
+	/**
917
+	 * Gets parameter information for a model regarding writing data
918
+	 *
919
+	 * @param string           $model_name
920
+	 * @param ModelVersionInfo $model_version_info
921
+	 * @param boolean          $create                                       whether this is for request to create (in
922
+	 *                                                                       which case we need all required params) or
923
+	 *                                                                       just to update (in which case we don't
924
+	 *                                                                       need those on every request)
925
+	 * @return array
926
+	 * @throws EE_Error
927
+	 * @throws ReflectionException
928
+	 */
929
+	protected function _get_write_params(
930
+		string $model_name,
931
+		ModelVersionInfo $model_version_info,
932
+		bool $create = false
933
+	): array {
934
+		$model     = EE_Registry::instance()->load_model($model_name);
935
+		$fields    = $model_version_info->fieldsOnModelInThisVersion($model);
936
+		$args_info = [];
937
+		foreach ($fields as $field_name => $field_obj) {
938
+			if ($field_obj->is_auto_increment()) {
939
+				// totally ignore auto increment IDs
940
+				continue;
941
+			}
942
+			$arg_info             = $field_obj->getSchema();
943
+			$required             = $create && ! $field_obj->is_nullable() && $field_obj->get_default_value() === null;
944
+			$arg_info['required'] = $required;
945
+			// remove the read-only flag. If it were read-only we wouldn't list it as an argument while writing, right?
946
+			unset($arg_info['readonly']);
947
+			$schema_properties = $field_obj->getSchemaProperties();
948
+			if (
949
+				isset($schema_properties['raw'])
950
+				&& $field_obj->getSchemaType() === 'object'
951
+			) {
952
+				// if there's a "raw" form of this argument, use those properties instead
953
+				$arg_info = array_replace(
954
+					$arg_info,
955
+					$schema_properties['raw']
956
+				);
957
+			}
958
+			$arg_info['default'] = ModelDataTranslator::prepareFieldValueForJson(
959
+				$field_obj,
960
+				$field_obj->get_default_value(),
961
+				$model_version_info->requestedVersion()
962
+			);
963
+			// we do our own validation and sanitization within the controller
964
+			if (function_exists('rest_validate_value_from_schema')) {
965
+				$sanitize_callback = [
966
+					'EED_Core_Rest_Api',
967
+					'default_sanitize_callback',
968
+				];
969
+			} else {
970
+				$sanitize_callback = null;
971
+			}
972
+			$arg_info['sanitize_callback'] = $sanitize_callback;
973
+			$args_info[ $field_name ]      = $arg_info;
974
+			if ($field_obj instanceof EE_Datetime_Field) {
975
+				$gmt_arg_info                      = $arg_info;
976
+				$gmt_arg_info['description']       = sprintf(
977
+					esc_html__(
978
+						'%1$s - the value for this field in UTC. Ignored if %2$s is provided.',
979
+						'event_espresso'
980
+					),
981
+					$field_obj->get_nicename(),
982
+					$field_name
983
+				);
984
+				$args_info[ $field_name . '_gmt' ] = $gmt_arg_info;
985
+			}
986
+		}
987
+		return $args_info;
988
+	}
989
+
990
+
991
+	/**
992
+	 * Replacement for WP API's 'rest_parse_request_arg'.
993
+	 * If the value is blank but not required, don't bother validating it.
994
+	 * Also, it uses our email validation instead of WP API's default.
995
+	 *
996
+	 * @param                 $value
997
+	 * @param WP_REST_Request $request
998
+	 * @param                 $param
999
+	 * @return bool|true|WP_Error
1000
+	 * @throws InvalidArgumentException
1001
+	 * @throws InvalidInterfaceException
1002
+	 * @throws InvalidDataTypeException
1003
+	 */
1004
+	public static function default_sanitize_callback($value, WP_REST_Request $request, $param)
1005
+	{
1006
+		$attributes = $request->get_attributes();
1007
+		if (
1008
+			! isset($attributes['args'][ $param ])
1009
+			|| ! is_array($attributes['args'][ $param ])
1010
+		) {
1011
+			$validation_result = true;
1012
+		} else {
1013
+			$args = $attributes['args'][ $param ];
1014
+			if (
1015
+				(
1016
+					$value === ''
1017
+					|| $value === null
1018
+				)
1019
+				&& (! isset($args['required'])
1020
+					|| $args['required'] === false
1021
+				)
1022
+			) {
1023
+				// not required and not provided? that's cool
1024
+				$validation_result = true;
1025
+			} elseif (
1026
+				isset($args['format'])
1027
+				&& $args['format'] === 'email'
1028
+			) {
1029
+				$validation_result = true;
1030
+				if (! self::_validate_email($value)) {
1031
+					$validation_result = new WP_Error(
1032
+						'rest_invalid_param',
1033
+						esc_html__(
1034
+							'The email address is not valid or does not exist.',
1035
+							'event_espresso'
1036
+						)
1037
+					);
1038
+				}
1039
+			} else {
1040
+				$validation_result = rest_validate_value_from_schema($value, $args, $param);
1041
+			}
1042
+		}
1043
+		if (is_wp_error($validation_result)) {
1044
+			return $validation_result;
1045
+		}
1046
+		return rest_sanitize_request_arg($value, $request, $param);
1047
+	}
1048
+
1049
+
1050
+	/**
1051
+	 * Returns whether or not this email address is valid. Copied from EE_Email_Validation_Strategy::_validate_email()
1052
+	 *
1053
+	 * @param $email
1054
+	 * @return bool
1055
+	 * @throws InvalidArgumentException
1056
+	 * @throws InvalidInterfaceException
1057
+	 * @throws InvalidDataTypeException
1058
+	 */
1059
+	protected static function _validate_email($email): bool
1060
+	{
1061
+		try {
1062
+			EmailAddressFactory::create($email);
1063
+			return true;
1064
+		} catch (EmailValidationException $e) {
1065
+			return false;
1066
+		}
1067
+	}
1068
+
1069
+
1070
+	/**
1071
+	 * Gets routes for the config
1072
+	 *
1073
+	 * @return array @see _register_model_routes
1074
+	 * @deprecated since version 4.9.1
1075
+	 */
1076
+	protected function _register_config_routes(): array
1077
+	{
1078
+		$config_routes = [];
1079
+		foreach (self::versions_served() as $version => $hidden_endpoint) {
1080
+			$config_routes[ self::ee_api_namespace . $version ] = $this->_get_config_route_data_for_version(
1081
+				$version,
1082
+				$hidden_endpoint
1083
+			);
1084
+		}
1085
+		return $config_routes;
1086
+	}
1087
+
1088
+
1089
+	/**
1090
+	 * Gets routes for the config for the specified version
1091
+	 *
1092
+	 * @param string  $version
1093
+	 * @param boolean $hidden_endpoint
1094
+	 * @return array
1095
+	 */
1096
+	protected function _get_config_route_data_for_version(string $version, bool $hidden_endpoint): array
1097
+	{
1098
+		return [
1099
+			'config'    => [
1100
+				[
1101
+					'callback'        => [
1102
+						'EventEspresso\core\libraries\rest_api\controllers\config\Read',
1103
+						'handleRequest',
1104
+					],
1105
+					'methods'         => WP_REST_Server::READABLE,
1106
+					'hidden_endpoint' => $hidden_endpoint,
1107
+					'callback_args'   => [$version],
1108
+				],
1109
+			],
1110
+			'site_info' => [
1111
+				[
1112
+					'callback'        => [
1113
+						'EventEspresso\core\libraries\rest_api\controllers\config\Read',
1114
+						'handleRequestSiteInfo',
1115
+					],
1116
+					'methods'         => WP_REST_Server::READABLE,
1117
+					'hidden_endpoint' => $hidden_endpoint,
1118
+					'callback_args'   => [$version],
1119
+				],
1120
+			],
1121
+		];
1122
+	}
1123
+
1124
+
1125
+	/**
1126
+	 * Gets the meta info routes
1127
+	 *
1128
+	 * @return array @see _register_model_routes
1129
+	 * @deprecated since version 4.9.1
1130
+	 */
1131
+	protected function _register_meta_routes(): array
1132
+	{
1133
+		$meta_routes = [];
1134
+		foreach (self::versions_served() as $version => $hidden_endpoint) {
1135
+			$meta_routes[ self::ee_api_namespace . $version ] = $this->_get_meta_route_data_for_version(
1136
+				$version,
1137
+				$hidden_endpoint
1138
+			);
1139
+		}
1140
+		return $meta_routes;
1141
+	}
1142
+
1143
+
1144
+	/**
1145
+	 * @param string  $version
1146
+	 * @param boolean $hidden_endpoint
1147
+	 * @return array
1148
+	 */
1149
+	protected function _get_meta_route_data_for_version(string $version, bool $hidden_endpoint = false): array
1150
+	{
1151
+		return [
1152
+			'resources' => [
1153
+				[
1154
+					'callback'        => [
1155
+						'EventEspresso\core\libraries\rest_api\controllers\model\Meta',
1156
+						'handleRequestModelsMeta',
1157
+					],
1158
+					'methods'         => WP_REST_Server::READABLE,
1159
+					'hidden_endpoint' => $hidden_endpoint,
1160
+					'callback_args'   => [$version],
1161
+				],
1162
+			],
1163
+		];
1164
+	}
1165
+
1166
+
1167
+	/**
1168
+	 * Tries to hide old 4.6 endpoints from the
1169
+	 *
1170
+	 * @param array $route_data
1171
+	 * @return array
1172
+	 * @throws EE_Error
1173
+	 * @throws ReflectionException
1174
+	 */
1175
+	public static function hide_old_endpoints(array $route_data): array
1176
+	{
1177
+		// allow API clients to override which endpoints get hidden, in case
1178
+		// they want to discover particular endpoints
1179
+		// also, we don't have access to the request so we have to just grab it from the super global
1180
+		$force_show_ee_namespace = ltrim(
1181
+			EED_Core_Rest_Api::getRequest()->getRequestParam('force_show_ee_namespace'),
1182
+			'/'
1183
+		);
1184
+		foreach (EED_Core_Rest_Api::get_ee_route_data() as $namespace => $relative_urls) {
1185
+			foreach ($relative_urls as $resource_name => $endpoints) {
1186
+				foreach ($endpoints as $key => $endpoint) {
1187
+					// skip schema and other route options
1188
+					if (! is_numeric($key)) {
1189
+						continue;
1190
+					}
1191
+					// by default, hide "hidden_endpoint"s, unless the request indicates
1192
+					// to $force_show_ee_namespace, in which case only show that one
1193
+					// namespace's endpoints (and hide all others)
1194
+					if (
1195
+						($force_show_ee_namespace !== '' && $force_show_ee_namespace !== $namespace)
1196
+						|| ($endpoint['hidden_endpoint'] && $force_show_ee_namespace === '')
1197
+					) {
1198
+						$full_route = '/' . ltrim($namespace, '/');
1199
+						$full_route .= '/' . ltrim($resource_name, '/');
1200
+						unset($route_data[ $full_route ]);
1201
+					}
1202
+				}
1203
+			}
1204
+		}
1205
+		return $route_data;
1206
+	}
1207
+
1208
+
1209
+	/**
1210
+	 * Returns an array describing which versions of core support serving requests for.
1211
+	 * Keys are core versions' major and minor version, and values are the
1212
+	 * LOWEST requested version they can serve. Eg, 4.7 can serve requests for 4.6-like
1213
+	 * data by just removing a few models and fields from the responses. However, 4.15 might remove
1214
+	 * the answers table entirely, in which case it would be very difficult for
1215
+	 * it to serve 4.6-style responses.
1216
+	 * Versions of core that are missing from this array are unknowns.
1217
+	 * previous ver
1218
+	 *
1219
+	 * @return array
1220
+	 */
1221
+	public static function version_compatibilities(): array
1222
+	{
1223
+		return apply_filters(
1224
+			'FHEE__EED_Core_REST_API__version_compatibilities',
1225
+			[
1226
+				'4.8.29' => '4.8.29',
1227
+				'4.8.33' => '4.8.29',
1228
+				'4.8.34' => '4.8.29',
1229
+				'4.8.36' => '4.8.29',
1230
+			]
1231
+		);
1232
+	}
1233
+
1234
+
1235
+	/**
1236
+	 * Gets the latest API version served. Eg if there
1237
+	 * are two versions served of the API, 4.8.29 and 4.8.32, and
1238
+	 * we are on core version 4.8.34, it will return the string "4.8.32"
1239
+	 *
1240
+	 * @return string
1241
+	 */
1242
+	public static function latest_rest_api_version(): string
1243
+	{
1244
+		$versions_served      = EED_Core_Rest_Api::versions_served();
1245
+		$versions_served_keys = array_keys($versions_served);
1246
+		return end($versions_served_keys);
1247
+	}
1248
+
1249
+
1250
+	/**
1251
+	 * Using EED_Core_Rest_Api::version_compatibilities(), determines what version of
1252
+	 * EE the API can serve requests for. Eg, if we are on 4.15 of core, and
1253
+	 * we can serve requests from 4.12 or later, this will return array( '4.12', '4.13', '4.14', '4.15' ).
1254
+	 * We also indicate whether or not this version should be put in the index or not
1255
+	 *
1256
+	 * @return array keys are API version numbers (just major and minor numbers), and values
1257
+	 * are whether or not they should be hidden
1258
+	 */
1259
+	public static function versions_served(): array
1260
+	{
1261
+		$versions_served           = [];
1262
+		$possibly_served_versions  = EED_Core_Rest_Api::version_compatibilities();
1263
+		$lowest_compatible_version = end($possibly_served_versions);
1264
+		reset($possibly_served_versions);
1265
+		$versions_served_historically = array_keys($possibly_served_versions);
1266
+		$latest_version               = end($versions_served_historically);
1267
+		reset($versions_served_historically);
1268
+		// for each version of core we have ever served:
1269
+		foreach ($versions_served_historically as $key_versioned_endpoint) {
1270
+			// if it's not above the current core version, and it's compatible with the current version of core
1271
+
1272
+			if ($key_versioned_endpoint === $latest_version) {
1273
+				// don't hide the latest version in the index
1274
+				$versions_served[ $key_versioned_endpoint ] = false;
1275
+			} elseif (
1276
+				version_compare($key_versioned_endpoint, $lowest_compatible_version, '>=')
1277
+				&& version_compare($key_versioned_endpoint, EED_Core_Rest_Api::core_version(), '<')
1278
+			) {
1279
+				// include, but hide, previous versions which are still supported
1280
+				$versions_served[ $key_versioned_endpoint ] = true;
1281
+			} elseif (
1282
+				apply_filters(
1283
+					'FHEE__EED_Core_Rest_Api__versions_served__include_incompatible_versions',
1284
+					false,
1285
+					$possibly_served_versions
1286
+				)
1287
+			) {
1288
+				// if a version is no longer supported, don't include it in index or list of versions served
1289
+				$versions_served[ $key_versioned_endpoint ] = true;
1290
+			}
1291
+		}
1292
+		return $versions_served;
1293
+	}
1294
+
1295
+
1296
+	/**
1297
+	 * Gets the major and minor version of EE core's version string
1298
+	 *
1299
+	 * @return string
1300
+	 */
1301
+	public static function core_version(): string
1302
+	{
1303
+		return apply_filters(
1304
+			'FHEE__EED_Core_REST_API__core_version',
1305
+			implode(
1306
+				'.',
1307
+				array_slice(
1308
+					explode(
1309
+						'.',
1310
+						espresso_version()
1311
+					),
1312
+					0,
1313
+					3
1314
+				)
1315
+			)
1316
+		);
1317
+	}
1318
+
1319
+
1320
+	/**
1321
+	 * Gets the default limit that should be used when querying for resources
1322
+	 *
1323
+	 * @return int
1324
+	 */
1325
+	public static function get_default_query_limit(): int
1326
+	{
1327
+		// we actually don't use a const because we want folks to always use
1328
+		// this method, not the const directly
1329
+		return apply_filters(
1330
+			'FHEE__EED_Core_Rest_Api__get_default_query_limit',
1331
+			50
1332
+		);
1333
+	}
1334
+
1335
+
1336
+	/**
1337
+	 *
1338
+	 * @param string $version api version string (i.e. '4.8.36')
1339
+	 * @return array
1340
+	 */
1341
+	public static function getCollectionRoutesIndexedByModelName(string $version = ''): array
1342
+	{
1343
+		$version           = empty($version) ? self::latest_rest_api_version() : $version;
1344
+		$model_names       = self::model_names_with_plural_routes($version);
1345
+		$collection_routes = [];
1346
+		foreach ($model_names as $model_name => $model_class_name) {
1347
+			$collection_routes[ strtolower($model_name) ] = '/' . self::ee_api_namespace . $version . '/'
1348
+															. EEH_Inflector::pluralize_and_lower($model_name);
1349
+		}
1350
+		return $collection_routes;
1351
+	}
1352
+
1353
+
1354
+	/**
1355
+	 * Returns an array of primary key names indexed by model names.
1356
+	 *
1357
+	 * @param string $version
1358
+	 * @return array
1359
+	 */
1360
+	public static function getPrimaryKeyNamesIndexedByModelName(string $version = ''): array
1361
+	{
1362
+		$version           = empty($version) ? self::latest_rest_api_version() : $version;
1363
+		$model_names       = self::model_names_with_plural_routes($version);
1364
+		$primary_key_items = [];
1365
+		foreach ($model_names as $model_name => $model_class_name) {
1366
+			$primary_keys = $model_class_name::instance()->get_combined_primary_key_fields();
1367
+			foreach ($primary_keys as $primary_key_name => $primary_key_field) {
1368
+				if (count($primary_keys) > 1) {
1369
+					$primary_key_items[ strtolower($model_name) ][] = $primary_key_name;
1370
+				} else {
1371
+					$primary_key_items[ strtolower($model_name) ] = $primary_key_name;
1372
+				}
1373
+			}
1374
+		}
1375
+		return $primary_key_items;
1376
+	}
1377
+
1378
+
1379
+	/**
1380
+	 * Determines the EE REST API debug mode is activated, or not.
1381
+	 *
1382
+	 * @return bool
1383
+	 * @since 4.9.76.p
1384
+	 */
1385
+	public static function debugMode(): ?bool
1386
+	{
1387
+		static $debug_mode = null; // could be class prop
1388
+		if ($debug_mode === null) {
1389
+			$debug_mode = defined('EE_REST_API_DEBUG_MODE') && EE_REST_API_DEBUG_MODE;
1390
+		}
1391
+		return $debug_mode;
1392
+	}
1393
+
1394
+
1395
+	/**
1396
+	 *    run - initial module setup
1397
+	 *
1398
+	 * @access    public
1399
+	 * @param WP $WP
1400
+	 * @return    void
1401
+	 */
1402
+	public function run($WP)
1403
+	{
1404
+	}
1405 1405
 }
Please login to merge, or discard this patch.
Spacing   +53 added lines, -53 removed lines patch added patch discarded remove patch
@@ -124,7 +124,7 @@  discard block
 block discarded – undo
124 124
      */
125 125
     protected static function _set_hooks_for_changes()
126 126
     {
127
-        $folder_contents = EEH_File::get_contents_of_folders([EE_LIBRARIES . 'rest_api/changes']);
127
+        $folder_contents = EEH_File::get_contents_of_folders([EE_LIBRARIES.'rest_api/changes']);
128 128
         foreach ($folder_contents as $classname_in_namespace => $filepath) {
129 129
             // ignore the base parent class
130 130
             // and legacy named classes
@@ -134,7 +134,7 @@  discard block
 block discarded – undo
134 134
             ) {
135 135
                 continue;
136 136
             }
137
-            $full_classname = 'EventEspresso\core\libraries\rest_api\changes\\' . $classname_in_namespace;
137
+            $full_classname = 'EventEspresso\core\libraries\rest_api\changes\\'.$classname_in_namespace;
138 138
             if (class_exists($full_classname)) {
139 139
                 $instance_of_class = new $full_classname();
140 140
                 if ($instance_of_class instanceof ChangesInBase) {
@@ -180,10 +180,10 @@  discard block
 block discarded – undo
180 180
                      * }
181 181
                      */
182 182
                     // skip route options
183
-                    if (! is_numeric($endpoint_key)) {
183
+                    if ( ! is_numeric($endpoint_key)) {
184 184
                         continue;
185 185
                     }
186
-                    if (! isset($data_for_single_endpoint['callback'], $data_for_single_endpoint['methods'])) {
186
+                    if ( ! isset($data_for_single_endpoint['callback'], $data_for_single_endpoint['methods'])) {
187 187
                         throw new EE_Error(
188 188
                             esc_html__(
189 189
                             // @codingStandardsIgnoreStart
@@ -203,7 +203,7 @@  discard block
 block discarded – undo
203 203
                     }
204 204
                     if (isset($data_for_single_endpoint['callback_args'])) {
205 205
                         $callback_args                    = $data_for_single_endpoint['callback_args'];
206
-                        $single_endpoint_args['callback'] = function (WP_REST_Request $request) use (
206
+                        $single_endpoint_args['callback'] = function(WP_REST_Request $request) use (
207 207
                             $callback,
208 208
                             $callback_args
209 209
                         ) {
@@ -220,7 +220,7 @@  discard block
 block discarded – undo
220 220
                     // the REST API will issue a _doing_it_wrong notice.
221 221
                     // Since the EE REST API defers capabilities to the db model system,
222 222
                     // we will just use the generic WP callback for public endpoints
223
-                    if (! isset($single_endpoint_args['permission_callback'])) {
223
+                    if ( ! isset($single_endpoint_args['permission_callback'])) {
224 224
                         $single_endpoint_args['permission_callback'] = '__return_true';
225 225
                     }
226 226
                     $multiple_endpoint_args[] = $single_endpoint_args;
@@ -229,7 +229,7 @@  discard block
 block discarded – undo
229 229
                     $schema_route_data                = $data_for_multiple_endpoints['schema'];
230 230
                     $schema_callback                  = $schema_route_data['schema_callback'];
231 231
                     $callback_args                    = $schema_route_data['callback_args'];
232
-                    $multiple_endpoint_args['schema'] = function () use ($schema_callback, $callback_args) {
232
+                    $multiple_endpoint_args['schema'] = function() use ($schema_callback, $callback_args) {
233 233
                         return call_user_func_array(
234 234
                             $schema_callback,
235 235
                             $callback_args
@@ -271,7 +271,7 @@  discard block
 block discarded – undo
271 271
     {
272 272
         // delete the saved EE REST API routes
273 273
         foreach (EED_Core_Rest_Api::versions_served() as $version => $hidden) {
274
-            delete_option(EED_Core_Rest_Api::saved_routes_option_names . $version);
274
+            delete_option(EED_Core_Rest_Api::saved_routes_option_names.$version);
275 275
         }
276 276
     }
277 277
 
@@ -291,7 +291,7 @@  discard block
 block discarded – undo
291 291
     {
292 292
         $ee_routes = [];
293 293
         foreach (self::versions_served() as $version => $hidden_endpoints) {
294
-            $ee_routes[ self::ee_api_namespace . $version ] = self::_get_ee_route_data_for_version(
294
+            $ee_routes[self::ee_api_namespace.$version] = self::_get_ee_route_data_for_version(
295 295
                 $version,
296 296
                 $hidden_endpoints
297 297
             );
@@ -312,8 +312,8 @@  discard block
 block discarded – undo
312 312
      */
313 313
     protected static function _get_ee_route_data_for_version(string $version, bool $hidden_endpoints = false): array
314 314
     {
315
-        $ee_routes = get_option(self::saved_routes_option_names . $version, null);
316
-        if (! $ee_routes || EED_Core_Rest_Api::debugMode()) {
315
+        $ee_routes = get_option(self::saved_routes_option_names.$version, null);
316
+        if ( ! $ee_routes || EED_Core_Rest_Api::debugMode()) {
317 317
             $ee_routes = self::_save_ee_route_data_for_version($version, $hidden_endpoints);
318 318
         }
319 319
         return $ee_routes;
@@ -341,7 +341,7 @@  discard block
 block discarded – undo
341 341
                 $instance->_get_rpc_route_data_for_version($version, $hidden_endpoints)
342 342
             )
343 343
         );
344
-        $option_name = self::saved_routes_option_names . $version;
344
+        $option_name = self::saved_routes_option_names.$version;
345 345
         if (get_option($option_name)) {
346 346
             update_option($option_name, $routes, true);
347 347
         } else {
@@ -388,8 +388,8 @@  discard block
 block discarded – undo
388 388
     {
389 389
         $model_routes = [];
390 390
         foreach (self::versions_served() as $version => $hidden_endpoint) {
391
-            $model_routes[ EED_Core_Rest_Api::ee_api_namespace
392
-                           . $version ] = $this->_get_config_route_data_for_version($version, $hidden_endpoint);
391
+            $model_routes[EED_Core_Rest_Api::ee_api_namespace
392
+                           . $version] = $this->_get_config_route_data_for_version($version, $hidden_endpoint);
393 393
         }
394 394
         return $model_routes;
395 395
     }
@@ -459,13 +459,13 @@  discard block
 block discarded – undo
459 459
         foreach (EED_Core_Rest_Api::model_names_with_plural_routes($version) as $model_name => $model_classname) {
460 460
             $model = EE_Registry::instance()->load_model($model_name);
461 461
             // if this isn't a valid model then let's skip iterate to the next item in the loop.
462
-            if (! $model instanceof EEM_Base) {
462
+            if ( ! $model instanceof EEM_Base) {
463 463
                 continue;
464 464
             }
465 465
             // yes we could just register one route for ALL models, but then they wouldn't show up in the index
466 466
             $plural_model_route                    = EED_Core_Rest_Api::get_collection_route($model);
467 467
             $singular_model_route                  = EED_Core_Rest_Api::get_entity_route($model, '(?P<id>[^\/]+)');
468
-            $model_routes[ $plural_model_route ]   = [
468
+            $model_routes[$plural_model_route]   = [
469 469
                 [
470 470
                     'callback'        => [
471 471
                         'EventEspresso\core\libraries\rest_api\controllers\model\Read',
@@ -476,7 +476,7 @@  discard block
 block discarded – undo
476 476
                     'hidden_endpoint' => $hidden_endpoint,
477 477
                     'args'            => $this->_get_read_query_params($model, $version),
478 478
                     '_links'          => [
479
-                        'self' => rest_url(EED_Core_Rest_Api::ee_api_namespace . $version . $singular_model_route),
479
+                        'self' => rest_url(EED_Core_Rest_Api::ee_api_namespace.$version.$singular_model_route),
480 480
                     ],
481 481
                 ],
482 482
                 'schema' => [
@@ -487,7 +487,7 @@  discard block
 block discarded – undo
487 487
                     'callback_args'   => [$version, $model_name],
488 488
                 ],
489 489
             ];
490
-            $model_routes[ $singular_model_route ] = [
490
+            $model_routes[$singular_model_route] = [
491 491
                 [
492 492
                     'callback'        => [
493 493
                         'EventEspresso\core\libraries\rest_api\controllers\model\Read',
@@ -506,7 +506,7 @@  discard block
 block discarded – undo
506 506
                     $model
507 507
                 )
508 508
             ) {
509
-                $model_routes[ $plural_model_route ][] = [
509
+                $model_routes[$plural_model_route][] = [
510 510
                     'callback'        => [
511 511
                         'EventEspresso\core\libraries\rest_api\controllers\model\Write',
512 512
                         'handleRequestInsert',
@@ -516,8 +516,8 @@  discard block
 block discarded – undo
516 516
                     'hidden_endpoint' => $hidden_endpoint,
517 517
                     'args'            => $this->_get_write_params($model_name, $model_version_info, true),
518 518
                 ];
519
-                $model_routes[ $singular_model_route ] = array_merge(
520
-                    $model_routes[ $singular_model_route ],
519
+                $model_routes[$singular_model_route] = array_merge(
520
+                    $model_routes[$singular_model_route],
521 521
                     [
522 522
                         [
523 523
                             'callback'        => [
@@ -543,12 +543,12 @@  discard block
 block discarded – undo
543 543
                 );
544 544
             }
545 545
             foreach ($model->relation_settings() as $relation_name => $relation_obj) {
546
-                $related_route                  = EED_Core_Rest_Api::get_relation_route_via(
546
+                $related_route = EED_Core_Rest_Api::get_relation_route_via(
547 547
                     $model,
548 548
                     '(?P<id>[^\/]+)',
549 549
                     $relation_obj
550 550
                 );
551
-                $model_routes[ $related_route ] = [
551
+                $model_routes[$related_route] = [
552 552
                     [
553 553
                         'callback'        => [
554 554
                             'EventEspresso\core\libraries\rest_api\controllers\model\Read',
@@ -561,8 +561,8 @@  discard block
 block discarded – undo
561 561
                     ],
562 562
                 ];
563 563
 
564
-                $related_write_route                  = $related_route . '/' . '(?P<related_id>[^\/]+)';
565
-                $model_routes[ $related_write_route ] = [
564
+                $related_write_route                  = $related_route.'/'.'(?P<related_id>[^\/]+)';
565
+                $model_routes[$related_write_route] = [
566 566
                     [
567 567
                         'callback'        => [
568 568
                             'EventEspresso\core\libraries\rest_api\controllers\model\Write',
@@ -619,7 +619,7 @@  discard block
 block discarded – undo
619 619
      */
620 620
     public static function get_entity_route(EEM_Base $model, $id): string
621 621
     {
622
-        return EED_Core_Rest_Api::get_collection_route($model) . '/' . $id;
622
+        return EED_Core_Rest_Api::get_collection_route($model).'/'.$id;
623 623
     }
624 624
 
625 625
 
@@ -639,7 +639,7 @@  discard block
 block discarded – undo
639 639
             $relation_obj->get_other_model()->get_this_model_name(),
640 640
             $relation_obj
641 641
         );
642
-        return EED_Core_Rest_Api::get_entity_route($model, $id) . '/' . $related_model_name_endpoint_part;
642
+        return EED_Core_Rest_Api::get_entity_route($model, $id).'/'.$related_model_name_endpoint_part;
643 643
     }
644 644
 
645 645
 
@@ -653,7 +653,7 @@  discard block
 block discarded – undo
653 653
      */
654 654
     public static function get_versioned_route_to(string $relative_route, string $version = '4.8.36'): string
655 655
     {
656
-        return '/' . EED_Core_Rest_Api::ee_api_namespace . $version . '/' . $relative_route;
656
+        return '/'.EED_Core_Rest_Api::ee_api_namespace.$version.'/'.$relative_route;
657 657
     }
658 658
 
659 659
 
@@ -667,7 +667,7 @@  discard block
 block discarded – undo
667 667
     {
668 668
         $routes = [];
669 669
         foreach (self::versions_served() as $version => $hidden_endpoint) {
670
-            $routes[ self::ee_api_namespace . $version ] = $this->_get_rpc_route_data_for_version(
670
+            $routes[self::ee_api_namespace.$version] = $this->_get_rpc_route_data_for_version(
671 671
                 $version,
672 672
                 $hidden_endpoint
673 673
             );
@@ -766,7 +766,7 @@  discard block
 block discarded – undo
766 766
      */
767 767
     protected function _get_delete_query_params(EEM_Base $model, string $version): array
768 768
     {
769
-        $params_for_delete          = [
769
+        $params_for_delete = [
770 770
             'allow_blocking' => [
771 771
                 'required' => false,
772 772
                 'default'  => true,
@@ -801,12 +801,12 @@  discard block
 block discarded – undo
801 801
     ): array {
802 802
         // if they're related through a HABTM relation, check for any non-FKs
803 803
         $all_relation_settings = $source_model->relation_settings();
804
-        $relation_settings     = $all_relation_settings[ $related_model->get_this_model_name() ];
804
+        $relation_settings     = $all_relation_settings[$related_model->get_this_model_name()];
805 805
         $params                = [];
806 806
         if ($relation_settings instanceof EE_HABTM_Relation && $relation_settings->hasNonKeyFields()) {
807 807
             foreach ($relation_settings->getNonKeyFields() as $field) {
808 808
                 /* @var $field EE_Model_Field_Base */
809
-                $params[ $field->get_name() ] = [
809
+                $params[$field->get_name()] = [
810 810
                     'required'          => ! $field->is_nullable(),
811 811
                     'default'           => ModelDataTranslator::prepareFieldValueForJson(
812 812
                         $field,
@@ -835,7 +835,7 @@  discard block
 block discarded – undo
835 835
     {
836 836
         $default_orderby = [];
837 837
         foreach ($model->get_combined_primary_key_fields() as $key_field) {
838
-            $default_orderby[ $key_field->get_name() ] = 'ASC';
838
+            $default_orderby[$key_field->get_name()] = 'ASC';
839 839
         }
840 840
         return array_merge(
841 841
             $this->_get_response_selection_query_params($model, $version),
@@ -869,7 +869,7 @@  discard block
 block discarded – undo
869 869
                     'type'              => [
870 870
                         'object',
871 871
                         'string',
872
-                    ],// because we accept a variety of types, WP core validation and sanitization
872
+                    ], // because we accept a variety of types, WP core validation and sanitization
873 873
                     // freaks out. We'll just validate this argument while handling the request
874 874
                     'validate_callback' => null,
875 875
                     'sanitize_callback' => null,
@@ -970,7 +970,7 @@  discard block
 block discarded – undo
970 970
                 $sanitize_callback = null;
971 971
             }
972 972
             $arg_info['sanitize_callback'] = $sanitize_callback;
973
-            $args_info[ $field_name ]      = $arg_info;
973
+            $args_info[$field_name]      = $arg_info;
974 974
             if ($field_obj instanceof EE_Datetime_Field) {
975 975
                 $gmt_arg_info                      = $arg_info;
976 976
                 $gmt_arg_info['description']       = sprintf(
@@ -981,7 +981,7 @@  discard block
 block discarded – undo
981 981
                     $field_obj->get_nicename(),
982 982
                     $field_name
983 983
                 );
984
-                $args_info[ $field_name . '_gmt' ] = $gmt_arg_info;
984
+                $args_info[$field_name.'_gmt'] = $gmt_arg_info;
985 985
             }
986 986
         }
987 987
         return $args_info;
@@ -1005,18 +1005,18 @@  discard block
 block discarded – undo
1005 1005
     {
1006 1006
         $attributes = $request->get_attributes();
1007 1007
         if (
1008
-            ! isset($attributes['args'][ $param ])
1009
-            || ! is_array($attributes['args'][ $param ])
1008
+            ! isset($attributes['args'][$param])
1009
+            || ! is_array($attributes['args'][$param])
1010 1010
         ) {
1011 1011
             $validation_result = true;
1012 1012
         } else {
1013
-            $args = $attributes['args'][ $param ];
1013
+            $args = $attributes['args'][$param];
1014 1014
             if (
1015 1015
                 (
1016 1016
                     $value === ''
1017 1017
                     || $value === null
1018 1018
                 )
1019
-                && (! isset($args['required'])
1019
+                && ( ! isset($args['required'])
1020 1020
                     || $args['required'] === false
1021 1021
                 )
1022 1022
             ) {
@@ -1027,7 +1027,7 @@  discard block
 block discarded – undo
1027 1027
                 && $args['format'] === 'email'
1028 1028
             ) {
1029 1029
                 $validation_result = true;
1030
-                if (! self::_validate_email($value)) {
1030
+                if ( ! self::_validate_email($value)) {
1031 1031
                     $validation_result = new WP_Error(
1032 1032
                         'rest_invalid_param',
1033 1033
                         esc_html__(
@@ -1077,7 +1077,7 @@  discard block
 block discarded – undo
1077 1077
     {
1078 1078
         $config_routes = [];
1079 1079
         foreach (self::versions_served() as $version => $hidden_endpoint) {
1080
-            $config_routes[ self::ee_api_namespace . $version ] = $this->_get_config_route_data_for_version(
1080
+            $config_routes[self::ee_api_namespace.$version] = $this->_get_config_route_data_for_version(
1081 1081
                 $version,
1082 1082
                 $hidden_endpoint
1083 1083
             );
@@ -1132,7 +1132,7 @@  discard block
 block discarded – undo
1132 1132
     {
1133 1133
         $meta_routes = [];
1134 1134
         foreach (self::versions_served() as $version => $hidden_endpoint) {
1135
-            $meta_routes[ self::ee_api_namespace . $version ] = $this->_get_meta_route_data_for_version(
1135
+            $meta_routes[self::ee_api_namespace.$version] = $this->_get_meta_route_data_for_version(
1136 1136
                 $version,
1137 1137
                 $hidden_endpoint
1138 1138
             );
@@ -1185,7 +1185,7 @@  discard block
 block discarded – undo
1185 1185
             foreach ($relative_urls as $resource_name => $endpoints) {
1186 1186
                 foreach ($endpoints as $key => $endpoint) {
1187 1187
                     // skip schema and other route options
1188
-                    if (! is_numeric($key)) {
1188
+                    if ( ! is_numeric($key)) {
1189 1189
                         continue;
1190 1190
                     }
1191 1191
                     // by default, hide "hidden_endpoint"s, unless the request indicates
@@ -1195,9 +1195,9 @@  discard block
 block discarded – undo
1195 1195
                         ($force_show_ee_namespace !== '' && $force_show_ee_namespace !== $namespace)
1196 1196
                         || ($endpoint['hidden_endpoint'] && $force_show_ee_namespace === '')
1197 1197
                     ) {
1198
-                        $full_route = '/' . ltrim($namespace, '/');
1199
-                        $full_route .= '/' . ltrim($resource_name, '/');
1200
-                        unset($route_data[ $full_route ]);
1198
+                        $full_route = '/'.ltrim($namespace, '/');
1199
+                        $full_route .= '/'.ltrim($resource_name, '/');
1200
+                        unset($route_data[$full_route]);
1201 1201
                     }
1202 1202
                 }
1203 1203
             }
@@ -1271,13 +1271,13 @@  discard block
 block discarded – undo
1271 1271
 
1272 1272
             if ($key_versioned_endpoint === $latest_version) {
1273 1273
                 // don't hide the latest version in the index
1274
-                $versions_served[ $key_versioned_endpoint ] = false;
1274
+                $versions_served[$key_versioned_endpoint] = false;
1275 1275
             } elseif (
1276 1276
                 version_compare($key_versioned_endpoint, $lowest_compatible_version, '>=')
1277 1277
                 && version_compare($key_versioned_endpoint, EED_Core_Rest_Api::core_version(), '<')
1278 1278
             ) {
1279 1279
                 // include, but hide, previous versions which are still supported
1280
-                $versions_served[ $key_versioned_endpoint ] = true;
1280
+                $versions_served[$key_versioned_endpoint] = true;
1281 1281
             } elseif (
1282 1282
                 apply_filters(
1283 1283
                     'FHEE__EED_Core_Rest_Api__versions_served__include_incompatible_versions',
@@ -1286,7 +1286,7 @@  discard block
 block discarded – undo
1286 1286
                 )
1287 1287
             ) {
1288 1288
                 // if a version is no longer supported, don't include it in index or list of versions served
1289
-                $versions_served[ $key_versioned_endpoint ] = true;
1289
+                $versions_served[$key_versioned_endpoint] = true;
1290 1290
             }
1291 1291
         }
1292 1292
         return $versions_served;
@@ -1344,7 +1344,7 @@  discard block
 block discarded – undo
1344 1344
         $model_names       = self::model_names_with_plural_routes($version);
1345 1345
         $collection_routes = [];
1346 1346
         foreach ($model_names as $model_name => $model_class_name) {
1347
-            $collection_routes[ strtolower($model_name) ] = '/' . self::ee_api_namespace . $version . '/'
1347
+            $collection_routes[strtolower($model_name)] = '/'.self::ee_api_namespace.$version.'/'
1348 1348
                                                             . EEH_Inflector::pluralize_and_lower($model_name);
1349 1349
         }
1350 1350
         return $collection_routes;
@@ -1366,9 +1366,9 @@  discard block
 block discarded – undo
1366 1366
             $primary_keys = $model_class_name::instance()->get_combined_primary_key_fields();
1367 1367
             foreach ($primary_keys as $primary_key_name => $primary_key_field) {
1368 1368
                 if (count($primary_keys) > 1) {
1369
-                    $primary_key_items[ strtolower($model_name) ][] = $primary_key_name;
1369
+                    $primary_key_items[strtolower($model_name)][] = $primary_key_name;
1370 1370
                 } else {
1371
-                    $primary_key_items[ strtolower($model_name) ] = $primary_key_name;
1371
+                    $primary_key_items[strtolower($model_name)] = $primary_key_name;
1372 1372
                 }
1373 1373
             }
1374 1374
         }
Please login to merge, or discard this patch.
core/services/activation/plugin_prompt/DownloadPluginPromptManager.php 1 patch
Indentation   +77 added lines, -77 removed lines patch added patch discarded remove patch
@@ -7,93 +7,93 @@  discard block
 block discarded – undo
7 7
 
8 8
 class DownloadPluginPromptManager
9 9
 {
10
-    /**
11
-     * @var CapabilitiesChecker
12
-     */
13
-    private $cap_checker;
10
+	/**
11
+	 * @var CapabilitiesChecker
12
+	 */
13
+	private $cap_checker;
14 14
 
15
-    /**
16
-     * @var DownloadPluginPrompt[]
17
-     */
18
-    private $plugin_prompts = [];
15
+	/**
16
+	 * @var DownloadPluginPrompt[]
17
+	 */
18
+	private $plugin_prompts = [];
19 19
 
20 20
 
21
-    /**
22
-     * @param CapabilitiesChecker $capabilities_checker
23
-     */
24
-    public function __construct(CapabilitiesChecker $capabilities_checker)
25
-    {
26
-        $this->cap_checker = $capabilities_checker;
27
-        $this->loadPluginPrompts();
28
-        if (! empty($this->plugin_prompts)) {
29
-            add_action('pre_current_active_plugins', [$this, 'displayPluginPrompts'], 100);
30
-        }
31
-    }
21
+	/**
22
+	 * @param CapabilitiesChecker $capabilities_checker
23
+	 */
24
+	public function __construct(CapabilitiesChecker $capabilities_checker)
25
+	{
26
+		$this->cap_checker = $capabilities_checker;
27
+		$this->loadPluginPrompts();
28
+		if (! empty($this->plugin_prompts)) {
29
+			add_action('pre_current_active_plugins', [$this, 'displayPluginPrompts'], 100);
30
+		}
31
+	}
32 32
 
33 33
 
34
-    public function loadPluginPrompts()
35
-    {
36
-        $this->wpGraphQlPrompt();
37
-        $this->restApiAuthPrompt();
38
-    }
34
+	public function loadPluginPrompts()
35
+	{
36
+		$this->wpGraphQlPrompt();
37
+		$this->restApiAuthPrompt();
38
+	}
39 39
 
40 40
 
41
-    public function displayPluginPrompts()
42
-    {
43
-        $notifications = '';
44
-        foreach ($this->plugin_prompts as $plugin_prompt) {
45
-            if ($this->cap_checker->processCapCheck($plugin_prompt->getCapCheck())) {
46
-                $notifications .= $plugin_prompt->displayNotification(true);
47
-            }
48
-        }
49
-        echo "
41
+	public function displayPluginPrompts()
42
+	{
43
+		$notifications = '';
44
+		foreach ($this->plugin_prompts as $plugin_prompt) {
45
+			if ($this->cap_checker->processCapCheck($plugin_prompt->getCapCheck())) {
46
+				$notifications .= $plugin_prompt->displayNotification(true);
47
+			}
48
+		}
49
+		echo "
50 50
         <div class='ee-download-plugin-prompts__grid'>
51 51
             " . wp_kses($notifications, AllowedTags::getWithFullTags()) . "
52 52
         </div>";
53
-    }
53
+	}
54 54
 
55 55
 
56
-    private function wpGraphQlPrompt()
57
-    {
58
-        if (class_exists('EventEspresso\core\services\graphql\GraphQLManager') && ! class_exists('WPGraphQL')) {
59
-            $this->plugin_prompts['WPGraphQL'] = new DownloadPluginPrompt(
60
-                'WPGraphQL',
61
-                'https://www.wpgraphql.com/',
62
-                "Event Espresso's new Advanced Editor",
63
-                null,
64
-                null,
65
-                'keen-performance.svg',
66
-                500
67
-            );
68
-        }
69
-    }
56
+	private function wpGraphQlPrompt()
57
+	{
58
+		if (class_exists('EventEspresso\core\services\graphql\GraphQLManager') && ! class_exists('WPGraphQL')) {
59
+			$this->plugin_prompts['WPGraphQL'] = new DownloadPluginPrompt(
60
+				'WPGraphQL',
61
+				'https://www.wpgraphql.com/',
62
+				"Event Espresso's new Advanced Editor",
63
+				null,
64
+				null,
65
+				'keen-performance.svg',
66
+				500
67
+			);
68
+		}
69
+	}
70 70
 
71 71
 
72
-    private function restApiAuthPrompt()
73
-    {
74
-        if (
75
-            class_exists('EED_Core_Rest_Api')
76
-            && ! (
77
-                is_readable(EE_THIRD_PARTY . 'wp-api-basic-auth/basic-auth.php')
78
-                || class_exists('Jwt_Auth')
79
-                || defined('MINIORANGE_API_AUTHENTICATION_VERSION')
80
-            )
81
-        ) {
82
-            $this->plugin_prompts['REST-API-Auth'] = new DownloadPluginPrompt(
83
-                'WP REST API Authentication',
84
-                '',
85
-                'The Event Espresso REST API',
86
-                esc_html__('REST API Authentication!', 'event_espresso'),
87
-                sprintf(
88
-                    /* translators: The Event Espresso REST API requires an Authentication plugin to protect your
72
+	private function restApiAuthPrompt()
73
+	{
74
+		if (
75
+			class_exists('EED_Core_Rest_Api')
76
+			&& ! (
77
+				is_readable(EE_THIRD_PARTY . 'wp-api-basic-auth/basic-auth.php')
78
+				|| class_exists('Jwt_Auth')
79
+				|| defined('MINIORANGE_API_AUTHENTICATION_VERSION')
80
+			)
81
+		) {
82
+			$this->plugin_prompts['REST-API-Auth'] = new DownloadPluginPrompt(
83
+				'WP REST API Authentication',
84
+				'',
85
+				'The Event Espresso REST API',
86
+				esc_html__('REST API Authentication!', 'event_espresso'),
87
+				sprintf(
88
+					/* translators: The Event Espresso REST API requires an Authentication plugin to protect your
89 89
                     site\'s data endpoints. We highly encourage you to secure your site using one of the following:
90 90
                     <list of plugins> */
91
-                    esc_html__(
92
-                        'The Event Espresso REST API requires an Authentication plugin to protect your site\'s data endpoints. We highly encourage you to secure your site using one of the following: %1$s',
93
-                        'event_espresso'
94
-                    ),
95
-                    // 'Plugin Name & Link'
96
-                    "
91
+					esc_html__(
92
+						'The Event Espresso REST API requires an Authentication plugin to protect your site\'s data endpoints. We highly encourage you to secure your site using one of the following: %1$s',
93
+						'event_espresso'
94
+					),
95
+					// 'Plugin Name & Link'
96
+					"
97 97
                     <ul>
98 98
                         <li>
99 99
                             <a href='https://github.com/WP-API/Basic-Auth' target='_blank'>
@@ -111,10 +111,10 @@  discard block
 block discarded – undo
111 111
                             </a>
112 112
                         </li>
113 113
                     </ul>"
114
-                ),
115
-                'password.svg',
116
-                570
117
-            );
118
-        }
119
-    }
114
+				),
115
+				'password.svg',
116
+				570
117
+			);
118
+		}
119
+	}
120 120
 }
Please login to merge, or discard this patch.
core/libraries/rest_api/ModelDataTranslator.php 2 patches
Indentation   +663 added lines, -663 removed lines patch added patch discarded remove patch
@@ -38,667 +38,667 @@
 block discarded – undo
38 38
  */
39 39
 class ModelDataTranslator
40 40
 {
41
-    /**
42
-     * We used to use -1 for infinity in the rest api, but that's ambiguous for
43
-     * fields that COULD contain -1; so we use null
44
-     */
45
-    const EE_INF_IN_REST = null;
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
-     * @throws EE_Error
58
-     */
59
-    public static function prepareFieldValuesFromJson(
60
-        EE_Model_Field_Base $field_obj,
61
-        $original_value_maybe_array,
62
-        string $requested_version,
63
-        string $timezone_string = 'UTC'
64
-    ) {
65
-        if (
66
-            is_array($original_value_maybe_array)
67
-            && ! $field_obj instanceof EE_Serialized_Text_Field
68
-        ) {
69
-            $new_value_maybe_array = [];
70
-            foreach ($original_value_maybe_array as $array_key => $array_item) {
71
-                $new_value_maybe_array[ $array_key ] = ModelDataTranslator::prepareFieldValueFromJson(
72
-                    $field_obj,
73
-                    $array_item,
74
-                    $requested_version,
75
-                    $timezone_string
76
-                );
77
-            }
78
-        } else {
79
-            $new_value_maybe_array = ModelDataTranslator::prepareFieldValueFromJson(
80
-                $field_obj,
81
-                $original_value_maybe_array,
82
-                $requested_version,
83
-                $timezone_string
84
-            );
85
-        }
86
-        return $new_value_maybe_array;
87
-    }
88
-
89
-
90
-    /**
91
-     * Prepares an array of field values FOR use in JSON/REST API
92
-     *
93
-     * @param EE_Model_Field_Base $field_obj
94
-     * @param mixed               $original_value_maybe_array
95
-     * @param string              $request_version (eg 4.8.36)
96
-     * @return mixed
97
-     * @throws EE_Error
98
-     * @throws EE_Error
99
-     */
100
-    public static function prepareFieldValuesForJson(
101
-        EE_Model_Field_Base $field_obj,
102
-        $original_value_maybe_array,
103
-        string $request_version
104
-    ) {
105
-        if (is_array($original_value_maybe_array)) {
106
-            $new_value = [];
107
-            foreach ($original_value_maybe_array as $key => $value) {
108
-                $new_value[ $key ] = ModelDataTranslator::prepareFieldValuesForJson(
109
-                    $field_obj,
110
-                    $value,
111
-                    $request_version
112
-                );
113
-            }
114
-        } else {
115
-            $new_value = ModelDataTranslator::prepareFieldValueForJson(
116
-                $field_obj,
117
-                $original_value_maybe_array,
118
-                $request_version
119
-            );
120
-        }
121
-        return $new_value;
122
-    }
123
-
124
-
125
-    /**
126
-     * Prepares incoming data from the json or request parameters for the models'
127
-     * "$query_params".
128
-     *
129
-     * @param EE_Model_Field_Base $field_obj
130
-     * @param mixed               $original_value
131
-     * @param string              $requested_version
132
-     * @param string              $timezone_string treat values as being in this timezone
133
-     * @return mixed
134
-     * @throws RestException
135
-     * @throws DomainException
136
-     * @throws EE_Error
137
-     */
138
-    public static function prepareFieldValueFromJson(
139
-        EE_Model_Field_Base $field_obj,
140
-        $original_value,
141
-        string $requested_version,
142
-        string $timezone_string = 'UTC'
143
-    ) {
144
-        // check if they accidentally submitted an error value. If so throw an exception
145
-        if (
146
-            is_array($original_value)
147
-            && isset($original_value['error_code'], $original_value['error_message'])
148
-        ) {
149
-            throw new RestException(
150
-                'rest_submitted_error_value',
151
-                sprintf(
152
-                    esc_html__(
153
-                        'You tried to submit a JSON error object as a value for %1$s. That\'s not allowed.',
154
-                        'event_espresso'
155
-                    ),
156
-                    $field_obj->get_name()
157
-                ),
158
-                [
159
-                    'status' => 400,
160
-                ]
161
-            );
162
-        }
163
-        // double-check for serialized PHP. We never accept serialized PHP. No way Jose.
164
-        ModelDataTranslator::throwExceptionIfContainsSerializedData($original_value);
165
-        $timezone_string =
166
-            $timezone_string !== ''
167
-                ? $timezone_string
168
-                : get_option('timezone_string', '');
169
-        // walk through the submitted data and double-check for serialized PHP. We never accept serialized PHP. No
170
-        // way Jose.
171
-        ModelDataTranslator::throwExceptionIfContainsSerializedData($original_value);
172
-        if (
173
-            $field_obj instanceof EE_Infinite_Integer_Field
174
-            && in_array($original_value, [null, ''], true)
175
-        ) {
176
-            $new_value = EE_INF;
177
-        } elseif ($field_obj instanceof EE_Datetime_Field) {
178
-            $new_value = rest_parse_date(
179
-                self::getTimestampWithTimezoneOffset($original_value, $field_obj, $timezone_string)
180
-            );
181
-            if ($new_value === false) {
182
-                throw new RestException(
183
-                    'invalid_format_for_timestamp',
184
-                    sprintf(
185
-                        esc_html__(
186
-                            '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.',
187
-                            'event_espresso'
188
-                        ),
189
-                        'RFC3339',
190
-                        'ISO8601',
191
-                        $original_value
192
-                    ),
193
-                    [
194
-                        'status' => 400,
195
-                    ]
196
-                );
197
-            }
198
-        } elseif ($field_obj instanceof EE_Boolean_Field) {
199
-            // Interpreted the strings "false", "true", "on", "off" appropriately.
200
-            $new_value = filter_var($original_value, FILTER_VALIDATE_BOOLEAN);
201
-        } else {
202
-            $new_value = $original_value;
203
-        }
204
-        return $new_value;
205
-    }
206
-
207
-
208
-    /**
209
-     * This checks if the incoming timestamp has timezone information already on it and if it doesn't then adds timezone
210
-     * information via details obtained from the host site.
211
-     *
212
-     * @param string            $original_timestamp
213
-     * @param EE_Datetime_Field $datetime_field
214
-     * @param                   $timezone_string
215
-     * @return string
216
-     * @throws DomainException
217
-     */
218
-    private static function getTimestampWithTimezoneOffset(
219
-        string $original_timestamp,
220
-        EE_Datetime_Field $datetime_field,
221
-        $timezone_string
222
-    ): string {
223
-        // already have timezone information?
224
-        if (preg_match('/Z|([+-])(\d{2}:\d{2})/', $original_timestamp)) {
225
-            // yes, we're ignoring the timezone.
226
-            return $original_timestamp;
227
-        }
228
-        // need to append timezone
229
-        [$offset_sign, $offset_secs] = self::parseTimezoneOffset(
230
-            $datetime_field->get_timezone_offset(
231
-                new DateTimeZone($timezone_string),
232
-                $original_timestamp
233
-            )
234
-        );
235
-        $offset_string =
236
-            str_pad(
237
-                floor($offset_secs / HOUR_IN_SECONDS),
238
-                2,
239
-                '0',
240
-                STR_PAD_LEFT
241
-            )
242
-            . ':'
243
-            . str_pad(
244
-                ($offset_secs % HOUR_IN_SECONDS) / MINUTE_IN_SECONDS,
245
-                2,
246
-                '0',
247
-                STR_PAD_LEFT
248
-            );
249
-        return $original_timestamp . $offset_sign . $offset_string;
250
-    }
251
-
252
-
253
-    /**
254
-     * Throws an exception if $data is a serialized PHP string (or somehow an actually PHP object, although I don't
255
-     * think that can happen). If $data is an array, will recurse into its keys and values
256
-     *
257
-     * @param mixed $data
258
-     * @return void
259
-     * @throws RestException
260
-     */
261
-    public static function throwExceptionIfContainsSerializedData($data)
262
-    {
263
-        if (is_array($data)) {
264
-            foreach ($data as $key => $value) {
265
-                ModelDataTranslator::throwExceptionIfContainsSerializedData($key);
266
-                ModelDataTranslator::throwExceptionIfContainsSerializedData($value);
267
-            }
268
-        } else {
269
-            if (is_serialized($data) || is_object($data)) {
270
-                throw new RestException(
271
-                    'serialized_data_submission_prohibited',
272
-                    esc_html__(
273
-                    // @codingStandardsIgnoreStart
274
-                        'You tried to submit a string of serialized text. Serialized PHP is prohibited over the EE4 REST API.',
275
-                        // @codingStandardsIgnoreEnd
276
-                        'event_espresso'
277
-                    )
278
-                );
279
-            }
280
-        }
281
-    }
282
-
283
-
284
-    /**
285
-     * determines what's going on with them timezone strings
286
-     *
287
-     * @param int|string $timezone_offset
288
-     * @return array
289
-     */
290
-    private static function parseTimezoneOffset($timezone_offset): array
291
-    {
292
-        $first_char = substr((string) $timezone_offset, 0, 1);
293
-        if ($first_char === '+' || $first_char === '-') {
294
-            $offset_sign = $first_char;
295
-            $offset_secs = substr((string) $timezone_offset, 1);
296
-        } else {
297
-            $offset_sign = '+';
298
-            $offset_secs = $timezone_offset;
299
-        }
300
-        return [$offset_sign, $offset_secs];
301
-    }
302
-
303
-
304
-    /**
305
-     * Prepares a field's value for display in the API
306
-     *
307
-     * @param EE_Model_Field_Base $field_obj
308
-     * @param mixed               $original_value
309
-     * @param string              $requested_version
310
-     * @return mixed
311
-     * @throws EE_Error
312
-     * @throws EE_Error
313
-     */
314
-    public static function prepareFieldValueForJson(
315
-        EE_Model_Field_Base $field_obj,
316
-        $original_value,
317
-        string $requested_version
318
-    ) {
319
-        if ($original_value === EE_INF) {
320
-            $new_value = ModelDataTranslator::EE_INF_IN_REST;
321
-        } elseif ($field_obj instanceof EE_Datetime_Field) {
322
-            if (is_string($original_value)) {
323
-                // did they submit a string of a unix timestamp?
324
-                if (is_numeric($original_value)) {
325
-                    $datetime_obj = new DateTime();
326
-                    $datetime_obj->setTimestamp((int) $original_value);
327
-                } else {
328
-                    // first, check if its a MySQL timestamp in GMT
329
-                    $datetime_obj = DateTime::createFromFormat('Y-m-d H:i:s', $original_value);
330
-                }
331
-                if (! $datetime_obj instanceof DateTime) {
332
-                    // so it's not a unix timestamp or a MySQL timestamp. Maybe its in the field's date/time format?
333
-                    $datetime_obj = $field_obj->prepare_for_set($original_value);
334
-                }
335
-                $original_value = $datetime_obj;
336
-            }
337
-            if ($original_value instanceof DateTime) {
338
-                $new_value = $original_value->format('Y-m-d H:i:s');
339
-            } elseif (is_int($original_value) || is_float($original_value)) {
340
-                $new_value = date('Y-m-d H:i:s', $original_value);
341
-            } elseif ($original_value === null || $original_value === '') {
342
-                $new_value = null;
343
-            } else {
344
-                // so it's not a datetime object, unix timestamp (as string or int),
345
-                // MySQL timestamp, or even a string in the field object's format. So no idea what it is
346
-                throw new EE_Error(
347
-                    sprintf(
348
-                        esc_html__(
349
-                        // @codingStandardsIgnoreStart
350
-                            '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".',
351
-                            // @codingStandardsIgnoreEnd
352
-                            'event_espresso'
353
-                        ),
354
-                        $original_value,
355
-                        $field_obj->get_name(),
356
-                        $field_obj->get_model_name(),
357
-                        $field_obj->get_time_format() . ' ' . $field_obj->get_time_format()
358
-                    )
359
-                );
360
-            }
361
-            if ($new_value !== null) {
362
-                // phpcs:disable PHPCompatibility.Extensions.RemovedExtensions.mysql_DeprecatedRemoved
363
-                $new_value = mysql_to_rfc3339($new_value);
364
-                // phpcs:enable
365
-            }
366
-        } else {
367
-            $new_value = $original_value;
368
-        }
369
-        // are we about to send an object? just don't. We have no good way to represent it in JSON.
370
-        // can't just check using is_object() because that missed PHP incomplete objects
371
-        if (! ModelDataTranslator::isRepresentableInJson($new_value)) {
372
-            $new_value = [
373
-                'error_code'    => 'php_object_not_return',
374
-                'error_message' => esc_html__(
375
-                    'The value of this field in the database is a PHP object, which can\'t be represented in JSON.',
376
-                    'event_espresso'
377
-                ),
378
-            ];
379
-        }
380
-        return apply_filters(
381
-            'FHEE__EventEspresso\core\libraries\rest_api\Model_Data_Translator__prepare_field_for_rest_api',
382
-            $new_value,
383
-            $field_obj,
384
-            $original_value,
385
-            $requested_version
386
-        );
387
-    }
388
-
389
-
390
-    /**
391
-     * Prepares condition-query-parameters (like what's in where and having) from
392
-     * the format expected in the API to use in the models
393
-     *
394
-     * @param array    $inputted_query_params_of_this_type
395
-     * @param EEM_Base $model
396
-     * @param string   $requested_version
397
-     * @param boolean  $writing whether this data will be written to the DB, or if we're just building a query.
398
-     *                          If we're writing to the DB, we don't expect any operators, or any logic query
399
-     *                          parameters, and we also won't accept serialized data unless the current user has
400
-     *                          unfiltered_html.
401
-     * @return array
402
-     * @throws DomainException
403
-     * @throws EE_Error
404
-     * @throws RestException
405
-     * @throws InvalidDataTypeException
406
-     * @throws InvalidInterfaceException
407
-     * @throws InvalidArgumentException
408
-     */
409
-    public static function prepareConditionsQueryParamsForModels(
410
-        array $inputted_query_params_of_this_type,
411
-        EEM_Base $model,
412
-        string $requested_version,
413
-        bool $writing = false
414
-    ): array {
415
-        $query_param_for_models = [];
416
-        $context                = new RestIncomingQueryParamContext($model, $requested_version, $writing);
417
-        foreach ($inputted_query_params_of_this_type as $query_param_key => $query_param_value) {
418
-            $query_param_meta = new RestIncomingQueryParamMetadata($query_param_key, $query_param_value, $context);
419
-            if ($query_param_meta->getField() instanceof EE_Model_Field_Base) {
420
-                $translated_value = $query_param_meta->determineConditionsQueryParameterValue();
421
-                if (
422
-                    (isset($query_param_for_models[ $query_param_meta->getQueryParamKey() ])
423
-                     && $query_param_meta->isGmtField())
424
-                    || $translated_value === null
425
-                ) {
426
-                    // they have already provided a non-gmt field, ignore the gmt one. That's what WP core
427
-                    // currently does (they might change it though). See https://core.trac.wordpress.org/ticket/39954
428
-                    // OR we couldn't create a translated value from their input
429
-                    continue;
430
-                }
431
-                $query_param_for_models[ $query_param_meta->getQueryParamKey() ] = $translated_value;
432
-            } else {
433
-                $nested_query_params = $query_param_meta->determineNestedConditionQueryParameters();
434
-                if ($nested_query_params) {
435
-                    $query_param_for_models[ $query_param_meta->getQueryParamKey() ] = $nested_query_params;
436
-                }
437
-            }
438
-        }
439
-        return $query_param_for_models;
440
-    }
441
-
442
-
443
-    /**
444
-     * Mostly checks if the last 4 characters are "_gmt", indicating its a
445
-     * gmt date field name
446
-     *
447
-     * @param string $field_name
448
-     * @return boolean
449
-     */
450
-    public static function isGmtDateFieldName(string $field_name): bool
451
-    {
452
-        $field_name = ModelDataTranslator::removeStarsAndAnythingAfterFromConditionQueryParamKey($field_name);
453
-        return substr($field_name, -4, 4) === '_gmt';
454
-    }
455
-
456
-
457
-    /**
458
-     * Removes the last "_gmt" part of a field name (and if there is no "_gmt" at the end, leave it alone)
459
-     *
460
-     * @param string $field_name
461
-     * @return string
462
-     */
463
-    public static function removeGmtFromFieldName(string $field_name): string
464
-    {
465
-        if (! ModelDataTranslator::isGmtDateFieldName($field_name)) {
466
-            return $field_name;
467
-        }
468
-        $query_param_sans_stars              =
469
-            ModelDataTranslator::removeStarsAndAnythingAfterFromConditionQueryParamKey(
470
-                $field_name
471
-            );
472
-        $query_param_sans_gmt_and_sans_stars = substr(
473
-            $query_param_sans_stars,
474
-            0,
475
-            strrpos(
476
-                $field_name,
477
-                '_gmt'
478
-            )
479
-        );
480
-        return str_replace($query_param_sans_stars, $query_param_sans_gmt_and_sans_stars, $field_name);
481
-    }
482
-
483
-
484
-    /**
485
-     * Takes a field name from the REST API and prepares it for the model querying
486
-     *
487
-     * @param string $field_name
488
-     * @return string
489
-     */
490
-    public static function prepareFieldNameFromJson(string $field_name): string
491
-    {
492
-        if (ModelDataTranslator::isGmtDateFieldName($field_name)) {
493
-            return ModelDataTranslator::removeGmtFromFieldName($field_name);
494
-        }
495
-        return $field_name;
496
-    }
497
-
498
-
499
-    /**
500
-     * Takes array of field names from REST API and prepares for models
501
-     *
502
-     * @param array $field_names
503
-     * @return array of field names (possibly include model prefixes)
504
-     */
505
-    public static function prepareFieldNamesFromJson(array $field_names): array
506
-    {
507
-        $new_array = [];
508
-        foreach ($field_names as $key => $field_name) {
509
-            $new_array[ $key ] = ModelDataTranslator::prepareFieldNameFromJson($field_name);
510
-        }
511
-        return $new_array;
512
-    }
513
-
514
-
515
-    /**
516
-     * Takes array where array keys are field names (possibly with model path prefixes)
517
-     * from the REST API and prepares them for model querying
518
-     *
519
-     * @param array $field_names_as_keys
520
-     * @return array
521
-     */
522
-    public static function prepareFieldNamesInArrayKeysFromJson(array $field_names_as_keys): array
523
-    {
524
-        $new_array = [];
525
-        foreach ($field_names_as_keys as $field_name => $value) {
526
-            $new_array[ ModelDataTranslator::prepareFieldNameFromJson($field_name) ] = $value;
527
-        }
528
-        return $new_array;
529
-    }
530
-
531
-
532
-    /**
533
-     * Prepares an array of model query params for use in the REST API
534
-     *
535
-     * @param array       $model_query_params
536
-     * @param EEM_Base    $model
537
-     * @param string|null $requested_version eg "4.8.36". If null is provided, defaults to the latest release of the EE4
538
-     *                                       REST API
539
-     * @return array which can be passed into the EE4 REST API when querying a model resource
540
-     * @throws EE_Error
541
-     * @throws ReflectionException
542
-     */
543
-    public static function prepareQueryParamsForRestApi(
544
-        array $model_query_params,
545
-        EEM_Base $model,
546
-        ?string $requested_version = null
547
-    ): array {
548
-        if ($requested_version === null) {
549
-            $requested_version = EED_Core_Rest_Api::latest_rest_api_version();
550
-        }
551
-        $rest_query_params = $model_query_params;
552
-        if (isset($model_query_params[0])) {
553
-            $rest_query_params['where'] = ModelDataTranslator::prepareConditionsQueryParamsForRestApi(
554
-                $model_query_params[0],
555
-                $model,
556
-                $requested_version
557
-            );
558
-            unset($rest_query_params[0]);
559
-        }
560
-        if (isset($model_query_params['having'])) {
561
-            $rest_query_params['having'] = ModelDataTranslator::prepareConditionsQueryParamsForRestApi(
562
-                $model_query_params['having'],
563
-                $model,
564
-                $requested_version
565
-            );
566
-        }
567
-        return apply_filters(
568
-            'FHEE__EventEspresso\core\libraries\rest_api\Model_Data_Translator__prepare_query_params_for_rest_api',
569
-            $rest_query_params,
570
-            $model_query_params,
571
-            $model,
572
-            $requested_version
573
-        );
574
-    }
575
-
576
-
577
-    /**
578
-     * Prepares all the sub-conditions query parameters (eg having or where conditions) for use in the rest api
579
-     *
580
-     * @param array    $inputted_query_params_of_this_type  eg like the "where" or "having" conditions query params
581
-     * @param EEM_Base $model
582
-     * @param string   $requested_version                   eg "4.8.36"
583
-     * @return array ready for use in the rest api query params
584
-     * @throws EE_Error
585
-     * @throws RestException if somehow a PHP object were in the query params' values,*@throws
586
-     * @throws ReflectionException
587
-     *                                                      ReflectionException
588
-     *                                                      (which would be really unusual)
589
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
590
-     */
591
-    public static function prepareConditionsQueryParamsForRestApi(
592
-        array $inputted_query_params_of_this_type,
593
-        EEM_Base $model,
594
-        string $requested_version
595
-    ): array {
596
-        $query_param_for_models = [];
597
-        foreach ($inputted_query_params_of_this_type as $query_param_key => $query_param_value) {
598
-            $field = ModelDataTranslator::deduceFieldFromQueryParam(
599
-                ModelDataTranslator::removeStarsAndAnythingAfterFromConditionQueryParamKey($query_param_key),
600
-                $model
601
-            );
602
-            if ($field instanceof EE_Model_Field_Base) {
603
-                // did they specify an operator?
604
-                if (is_array($query_param_value)) {
605
-                    $op               = $query_param_value[0];
606
-                    $translated_value = [$op];
607
-                    if (isset($query_param_value[1])) {
608
-                        $value               = $query_param_value[1];
609
-                        $translated_value[1] = ModelDataTranslator::prepareFieldValuesForJson(
610
-                            $field,
611
-                            $value,
612
-                            $requested_version
613
-                        );
614
-                    }
615
-                } else {
616
-                    $translated_value = ModelDataTranslator::prepareFieldValueForJson(
617
-                        $field,
618
-                        $query_param_value,
619
-                        $requested_version
620
-                    );
621
-                }
622
-                $query_param_for_models[ $query_param_key ] = $translated_value;
623
-            } else {
624
-                // so it's not for a field, assume it's a logic query param key
625
-                $query_param_for_models[ $query_param_key ] =
626
-                    ModelDataTranslator::prepareConditionsQueryParamsForRestApi(
627
-                        $query_param_value,
628
-                        $model,
629
-                        $requested_version
630
-                    );
631
-            }
632
-        }
633
-        return $query_param_for_models;
634
-    }
635
-
636
-
637
-    /**
638
-     * @param $condition_query_param_key
639
-     * @return string
640
-     */
641
-    public static function removeStarsAndAnythingAfterFromConditionQueryParamKey($condition_query_param_key): string
642
-    {
643
-        $pos_of_star = strpos($condition_query_param_key, '*');
644
-        if ($pos_of_star === false) {
645
-            return $condition_query_param_key;
646
-        }
647
-        return substr($condition_query_param_key, 0, $pos_of_star);
648
-    }
649
-
650
-
651
-    /**
652
-     * Takes the input parameter and finds the model field that it indicates.
653
-     *
654
-     * @param string   $query_param_name like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
655
-     * @param EEM_Base $model
656
-     * @return EE_Model_Field_Base
657
-     * @throws EE_Error
658
-     * @throws ReflectionException
659
-     */
660
-    public static function deduceFieldFromQueryParam(string $query_param_name, EEM_Base $model): ?EE_Model_Field_Base
661
-    {
662
-        // ok, now proceed with deducing which part is the model's name, and which is the field's name
663
-        // which will help us find the database table and column
664
-        $query_param_parts = explode('.', $query_param_name);
665
-        if (empty($query_param_parts)) {
666
-            throw new EE_Error(
667
-                sprintf(
668
-                    esc_html__(
669
-                        '_extract_column_name is empty when trying to extract column and table name from %s',
670
-                        'event_espresso'
671
-                    ),
672
-                    $query_param_name
673
-                )
674
-            );
675
-        }
676
-        $number_of_parts       = count($query_param_parts);
677
-        $last_query_param_part = $query_param_parts[ count($query_param_parts) - 1 ];
678
-        $field_name            = $last_query_param_part;
679
-        if ($number_of_parts !== 1) {
680
-            // the last part is the column name, and there are only 2parts. therefore...
681
-            $model = EE_Registry::instance()->load_model($query_param_parts[ $number_of_parts - 2 ]);
682
-        }
683
-        try {
684
-            return $model->field_settings_for($field_name, false);
685
-        } catch (EE_Error $e) {
686
-            return null;
687
-        }
688
-    }
689
-
690
-
691
-    /**
692
-     * Returns true if $data can be easily represented in JSON.
693
-     * Basically, objects and resources can't be represented in JSON easily.
694
-     *
695
-     * @param mixed $data
696
-     * @return bool
697
-     */
698
-    protected static function isRepresentableInJson($data): bool
699
-    {
700
-        return is_scalar($data)
701
-               || is_array($data)
702
-               || is_null($data);
703
-    }
41
+	/**
42
+	 * We used to use -1 for infinity in the rest api, but that's ambiguous for
43
+	 * fields that COULD contain -1; so we use null
44
+	 */
45
+	const EE_INF_IN_REST = null;
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
+	 * @throws EE_Error
58
+	 */
59
+	public static function prepareFieldValuesFromJson(
60
+		EE_Model_Field_Base $field_obj,
61
+		$original_value_maybe_array,
62
+		string $requested_version,
63
+		string $timezone_string = 'UTC'
64
+	) {
65
+		if (
66
+			is_array($original_value_maybe_array)
67
+			&& ! $field_obj instanceof EE_Serialized_Text_Field
68
+		) {
69
+			$new_value_maybe_array = [];
70
+			foreach ($original_value_maybe_array as $array_key => $array_item) {
71
+				$new_value_maybe_array[ $array_key ] = ModelDataTranslator::prepareFieldValueFromJson(
72
+					$field_obj,
73
+					$array_item,
74
+					$requested_version,
75
+					$timezone_string
76
+				);
77
+			}
78
+		} else {
79
+			$new_value_maybe_array = ModelDataTranslator::prepareFieldValueFromJson(
80
+				$field_obj,
81
+				$original_value_maybe_array,
82
+				$requested_version,
83
+				$timezone_string
84
+			);
85
+		}
86
+		return $new_value_maybe_array;
87
+	}
88
+
89
+
90
+	/**
91
+	 * Prepares an array of field values FOR use in JSON/REST API
92
+	 *
93
+	 * @param EE_Model_Field_Base $field_obj
94
+	 * @param mixed               $original_value_maybe_array
95
+	 * @param string              $request_version (eg 4.8.36)
96
+	 * @return mixed
97
+	 * @throws EE_Error
98
+	 * @throws EE_Error
99
+	 */
100
+	public static function prepareFieldValuesForJson(
101
+		EE_Model_Field_Base $field_obj,
102
+		$original_value_maybe_array,
103
+		string $request_version
104
+	) {
105
+		if (is_array($original_value_maybe_array)) {
106
+			$new_value = [];
107
+			foreach ($original_value_maybe_array as $key => $value) {
108
+				$new_value[ $key ] = ModelDataTranslator::prepareFieldValuesForJson(
109
+					$field_obj,
110
+					$value,
111
+					$request_version
112
+				);
113
+			}
114
+		} else {
115
+			$new_value = ModelDataTranslator::prepareFieldValueForJson(
116
+				$field_obj,
117
+				$original_value_maybe_array,
118
+				$request_version
119
+			);
120
+		}
121
+		return $new_value;
122
+	}
123
+
124
+
125
+	/**
126
+	 * Prepares incoming data from the json or request parameters for the models'
127
+	 * "$query_params".
128
+	 *
129
+	 * @param EE_Model_Field_Base $field_obj
130
+	 * @param mixed               $original_value
131
+	 * @param string              $requested_version
132
+	 * @param string              $timezone_string treat values as being in this timezone
133
+	 * @return mixed
134
+	 * @throws RestException
135
+	 * @throws DomainException
136
+	 * @throws EE_Error
137
+	 */
138
+	public static function prepareFieldValueFromJson(
139
+		EE_Model_Field_Base $field_obj,
140
+		$original_value,
141
+		string $requested_version,
142
+		string $timezone_string = 'UTC'
143
+	) {
144
+		// check if they accidentally submitted an error value. If so throw an exception
145
+		if (
146
+			is_array($original_value)
147
+			&& isset($original_value['error_code'], $original_value['error_message'])
148
+		) {
149
+			throw new RestException(
150
+				'rest_submitted_error_value',
151
+				sprintf(
152
+					esc_html__(
153
+						'You tried to submit a JSON error object as a value for %1$s. That\'s not allowed.',
154
+						'event_espresso'
155
+					),
156
+					$field_obj->get_name()
157
+				),
158
+				[
159
+					'status' => 400,
160
+				]
161
+			);
162
+		}
163
+		// double-check for serialized PHP. We never accept serialized PHP. No way Jose.
164
+		ModelDataTranslator::throwExceptionIfContainsSerializedData($original_value);
165
+		$timezone_string =
166
+			$timezone_string !== ''
167
+				? $timezone_string
168
+				: get_option('timezone_string', '');
169
+		// walk through the submitted data and double-check for serialized PHP. We never accept serialized PHP. No
170
+		// way Jose.
171
+		ModelDataTranslator::throwExceptionIfContainsSerializedData($original_value);
172
+		if (
173
+			$field_obj instanceof EE_Infinite_Integer_Field
174
+			&& in_array($original_value, [null, ''], true)
175
+		) {
176
+			$new_value = EE_INF;
177
+		} elseif ($field_obj instanceof EE_Datetime_Field) {
178
+			$new_value = rest_parse_date(
179
+				self::getTimestampWithTimezoneOffset($original_value, $field_obj, $timezone_string)
180
+			);
181
+			if ($new_value === false) {
182
+				throw new RestException(
183
+					'invalid_format_for_timestamp',
184
+					sprintf(
185
+						esc_html__(
186
+							'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.',
187
+							'event_espresso'
188
+						),
189
+						'RFC3339',
190
+						'ISO8601',
191
+						$original_value
192
+					),
193
+					[
194
+						'status' => 400,
195
+					]
196
+				);
197
+			}
198
+		} elseif ($field_obj instanceof EE_Boolean_Field) {
199
+			// Interpreted the strings "false", "true", "on", "off" appropriately.
200
+			$new_value = filter_var($original_value, FILTER_VALIDATE_BOOLEAN);
201
+		} else {
202
+			$new_value = $original_value;
203
+		}
204
+		return $new_value;
205
+	}
206
+
207
+
208
+	/**
209
+	 * This checks if the incoming timestamp has timezone information already on it and if it doesn't then adds timezone
210
+	 * information via details obtained from the host site.
211
+	 *
212
+	 * @param string            $original_timestamp
213
+	 * @param EE_Datetime_Field $datetime_field
214
+	 * @param                   $timezone_string
215
+	 * @return string
216
+	 * @throws DomainException
217
+	 */
218
+	private static function getTimestampWithTimezoneOffset(
219
+		string $original_timestamp,
220
+		EE_Datetime_Field $datetime_field,
221
+		$timezone_string
222
+	): string {
223
+		// already have timezone information?
224
+		if (preg_match('/Z|([+-])(\d{2}:\d{2})/', $original_timestamp)) {
225
+			// yes, we're ignoring the timezone.
226
+			return $original_timestamp;
227
+		}
228
+		// need to append timezone
229
+		[$offset_sign, $offset_secs] = self::parseTimezoneOffset(
230
+			$datetime_field->get_timezone_offset(
231
+				new DateTimeZone($timezone_string),
232
+				$original_timestamp
233
+			)
234
+		);
235
+		$offset_string =
236
+			str_pad(
237
+				floor($offset_secs / HOUR_IN_SECONDS),
238
+				2,
239
+				'0',
240
+				STR_PAD_LEFT
241
+			)
242
+			. ':'
243
+			. str_pad(
244
+				($offset_secs % HOUR_IN_SECONDS) / MINUTE_IN_SECONDS,
245
+				2,
246
+				'0',
247
+				STR_PAD_LEFT
248
+			);
249
+		return $original_timestamp . $offset_sign . $offset_string;
250
+	}
251
+
252
+
253
+	/**
254
+	 * Throws an exception if $data is a serialized PHP string (or somehow an actually PHP object, although I don't
255
+	 * think that can happen). If $data is an array, will recurse into its keys and values
256
+	 *
257
+	 * @param mixed $data
258
+	 * @return void
259
+	 * @throws RestException
260
+	 */
261
+	public static function throwExceptionIfContainsSerializedData($data)
262
+	{
263
+		if (is_array($data)) {
264
+			foreach ($data as $key => $value) {
265
+				ModelDataTranslator::throwExceptionIfContainsSerializedData($key);
266
+				ModelDataTranslator::throwExceptionIfContainsSerializedData($value);
267
+			}
268
+		} else {
269
+			if (is_serialized($data) || is_object($data)) {
270
+				throw new RestException(
271
+					'serialized_data_submission_prohibited',
272
+					esc_html__(
273
+					// @codingStandardsIgnoreStart
274
+						'You tried to submit a string of serialized text. Serialized PHP is prohibited over the EE4 REST API.',
275
+						// @codingStandardsIgnoreEnd
276
+						'event_espresso'
277
+					)
278
+				);
279
+			}
280
+		}
281
+	}
282
+
283
+
284
+	/**
285
+	 * determines what's going on with them timezone strings
286
+	 *
287
+	 * @param int|string $timezone_offset
288
+	 * @return array
289
+	 */
290
+	private static function parseTimezoneOffset($timezone_offset): array
291
+	{
292
+		$first_char = substr((string) $timezone_offset, 0, 1);
293
+		if ($first_char === '+' || $first_char === '-') {
294
+			$offset_sign = $first_char;
295
+			$offset_secs = substr((string) $timezone_offset, 1);
296
+		} else {
297
+			$offset_sign = '+';
298
+			$offset_secs = $timezone_offset;
299
+		}
300
+		return [$offset_sign, $offset_secs];
301
+	}
302
+
303
+
304
+	/**
305
+	 * Prepares a field's value for display in the API
306
+	 *
307
+	 * @param EE_Model_Field_Base $field_obj
308
+	 * @param mixed               $original_value
309
+	 * @param string              $requested_version
310
+	 * @return mixed
311
+	 * @throws EE_Error
312
+	 * @throws EE_Error
313
+	 */
314
+	public static function prepareFieldValueForJson(
315
+		EE_Model_Field_Base $field_obj,
316
+		$original_value,
317
+		string $requested_version
318
+	) {
319
+		if ($original_value === EE_INF) {
320
+			$new_value = ModelDataTranslator::EE_INF_IN_REST;
321
+		} elseif ($field_obj instanceof EE_Datetime_Field) {
322
+			if (is_string($original_value)) {
323
+				// did they submit a string of a unix timestamp?
324
+				if (is_numeric($original_value)) {
325
+					$datetime_obj = new DateTime();
326
+					$datetime_obj->setTimestamp((int) $original_value);
327
+				} else {
328
+					// first, check if its a MySQL timestamp in GMT
329
+					$datetime_obj = DateTime::createFromFormat('Y-m-d H:i:s', $original_value);
330
+				}
331
+				if (! $datetime_obj instanceof DateTime) {
332
+					// so it's not a unix timestamp or a MySQL timestamp. Maybe its in the field's date/time format?
333
+					$datetime_obj = $field_obj->prepare_for_set($original_value);
334
+				}
335
+				$original_value = $datetime_obj;
336
+			}
337
+			if ($original_value instanceof DateTime) {
338
+				$new_value = $original_value->format('Y-m-d H:i:s');
339
+			} elseif (is_int($original_value) || is_float($original_value)) {
340
+				$new_value = date('Y-m-d H:i:s', $original_value);
341
+			} elseif ($original_value === null || $original_value === '') {
342
+				$new_value = null;
343
+			} else {
344
+				// so it's not a datetime object, unix timestamp (as string or int),
345
+				// MySQL timestamp, or even a string in the field object's format. So no idea what it is
346
+				throw new EE_Error(
347
+					sprintf(
348
+						esc_html__(
349
+						// @codingStandardsIgnoreStart
350
+							'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".',
351
+							// @codingStandardsIgnoreEnd
352
+							'event_espresso'
353
+						),
354
+						$original_value,
355
+						$field_obj->get_name(),
356
+						$field_obj->get_model_name(),
357
+						$field_obj->get_time_format() . ' ' . $field_obj->get_time_format()
358
+					)
359
+				);
360
+			}
361
+			if ($new_value !== null) {
362
+				// phpcs:disable PHPCompatibility.Extensions.RemovedExtensions.mysql_DeprecatedRemoved
363
+				$new_value = mysql_to_rfc3339($new_value);
364
+				// phpcs:enable
365
+			}
366
+		} else {
367
+			$new_value = $original_value;
368
+		}
369
+		// are we about to send an object? just don't. We have no good way to represent it in JSON.
370
+		// can't just check using is_object() because that missed PHP incomplete objects
371
+		if (! ModelDataTranslator::isRepresentableInJson($new_value)) {
372
+			$new_value = [
373
+				'error_code'    => 'php_object_not_return',
374
+				'error_message' => esc_html__(
375
+					'The value of this field in the database is a PHP object, which can\'t be represented in JSON.',
376
+					'event_espresso'
377
+				),
378
+			];
379
+		}
380
+		return apply_filters(
381
+			'FHEE__EventEspresso\core\libraries\rest_api\Model_Data_Translator__prepare_field_for_rest_api',
382
+			$new_value,
383
+			$field_obj,
384
+			$original_value,
385
+			$requested_version
386
+		);
387
+	}
388
+
389
+
390
+	/**
391
+	 * Prepares condition-query-parameters (like what's in where and having) from
392
+	 * the format expected in the API to use in the models
393
+	 *
394
+	 * @param array    $inputted_query_params_of_this_type
395
+	 * @param EEM_Base $model
396
+	 * @param string   $requested_version
397
+	 * @param boolean  $writing whether this data will be written to the DB, or if we're just building a query.
398
+	 *                          If we're writing to the DB, we don't expect any operators, or any logic query
399
+	 *                          parameters, and we also won't accept serialized data unless the current user has
400
+	 *                          unfiltered_html.
401
+	 * @return array
402
+	 * @throws DomainException
403
+	 * @throws EE_Error
404
+	 * @throws RestException
405
+	 * @throws InvalidDataTypeException
406
+	 * @throws InvalidInterfaceException
407
+	 * @throws InvalidArgumentException
408
+	 */
409
+	public static function prepareConditionsQueryParamsForModels(
410
+		array $inputted_query_params_of_this_type,
411
+		EEM_Base $model,
412
+		string $requested_version,
413
+		bool $writing = false
414
+	): array {
415
+		$query_param_for_models = [];
416
+		$context                = new RestIncomingQueryParamContext($model, $requested_version, $writing);
417
+		foreach ($inputted_query_params_of_this_type as $query_param_key => $query_param_value) {
418
+			$query_param_meta = new RestIncomingQueryParamMetadata($query_param_key, $query_param_value, $context);
419
+			if ($query_param_meta->getField() instanceof EE_Model_Field_Base) {
420
+				$translated_value = $query_param_meta->determineConditionsQueryParameterValue();
421
+				if (
422
+					(isset($query_param_for_models[ $query_param_meta->getQueryParamKey() ])
423
+					 && $query_param_meta->isGmtField())
424
+					|| $translated_value === null
425
+				) {
426
+					// they have already provided a non-gmt field, ignore the gmt one. That's what WP core
427
+					// currently does (they might change it though). See https://core.trac.wordpress.org/ticket/39954
428
+					// OR we couldn't create a translated value from their input
429
+					continue;
430
+				}
431
+				$query_param_for_models[ $query_param_meta->getQueryParamKey() ] = $translated_value;
432
+			} else {
433
+				$nested_query_params = $query_param_meta->determineNestedConditionQueryParameters();
434
+				if ($nested_query_params) {
435
+					$query_param_for_models[ $query_param_meta->getQueryParamKey() ] = $nested_query_params;
436
+				}
437
+			}
438
+		}
439
+		return $query_param_for_models;
440
+	}
441
+
442
+
443
+	/**
444
+	 * Mostly checks if the last 4 characters are "_gmt", indicating its a
445
+	 * gmt date field name
446
+	 *
447
+	 * @param string $field_name
448
+	 * @return boolean
449
+	 */
450
+	public static function isGmtDateFieldName(string $field_name): bool
451
+	{
452
+		$field_name = ModelDataTranslator::removeStarsAndAnythingAfterFromConditionQueryParamKey($field_name);
453
+		return substr($field_name, -4, 4) === '_gmt';
454
+	}
455
+
456
+
457
+	/**
458
+	 * Removes the last "_gmt" part of a field name (and if there is no "_gmt" at the end, leave it alone)
459
+	 *
460
+	 * @param string $field_name
461
+	 * @return string
462
+	 */
463
+	public static function removeGmtFromFieldName(string $field_name): string
464
+	{
465
+		if (! ModelDataTranslator::isGmtDateFieldName($field_name)) {
466
+			return $field_name;
467
+		}
468
+		$query_param_sans_stars              =
469
+			ModelDataTranslator::removeStarsAndAnythingAfterFromConditionQueryParamKey(
470
+				$field_name
471
+			);
472
+		$query_param_sans_gmt_and_sans_stars = substr(
473
+			$query_param_sans_stars,
474
+			0,
475
+			strrpos(
476
+				$field_name,
477
+				'_gmt'
478
+			)
479
+		);
480
+		return str_replace($query_param_sans_stars, $query_param_sans_gmt_and_sans_stars, $field_name);
481
+	}
482
+
483
+
484
+	/**
485
+	 * Takes a field name from the REST API and prepares it for the model querying
486
+	 *
487
+	 * @param string $field_name
488
+	 * @return string
489
+	 */
490
+	public static function prepareFieldNameFromJson(string $field_name): string
491
+	{
492
+		if (ModelDataTranslator::isGmtDateFieldName($field_name)) {
493
+			return ModelDataTranslator::removeGmtFromFieldName($field_name);
494
+		}
495
+		return $field_name;
496
+	}
497
+
498
+
499
+	/**
500
+	 * Takes array of field names from REST API and prepares for models
501
+	 *
502
+	 * @param array $field_names
503
+	 * @return array of field names (possibly include model prefixes)
504
+	 */
505
+	public static function prepareFieldNamesFromJson(array $field_names): array
506
+	{
507
+		$new_array = [];
508
+		foreach ($field_names as $key => $field_name) {
509
+			$new_array[ $key ] = ModelDataTranslator::prepareFieldNameFromJson($field_name);
510
+		}
511
+		return $new_array;
512
+	}
513
+
514
+
515
+	/**
516
+	 * Takes array where array keys are field names (possibly with model path prefixes)
517
+	 * from the REST API and prepares them for model querying
518
+	 *
519
+	 * @param array $field_names_as_keys
520
+	 * @return array
521
+	 */
522
+	public static function prepareFieldNamesInArrayKeysFromJson(array $field_names_as_keys): array
523
+	{
524
+		$new_array = [];
525
+		foreach ($field_names_as_keys as $field_name => $value) {
526
+			$new_array[ ModelDataTranslator::prepareFieldNameFromJson($field_name) ] = $value;
527
+		}
528
+		return $new_array;
529
+	}
530
+
531
+
532
+	/**
533
+	 * Prepares an array of model query params for use in the REST API
534
+	 *
535
+	 * @param array       $model_query_params
536
+	 * @param EEM_Base    $model
537
+	 * @param string|null $requested_version eg "4.8.36". If null is provided, defaults to the latest release of the EE4
538
+	 *                                       REST API
539
+	 * @return array which can be passed into the EE4 REST API when querying a model resource
540
+	 * @throws EE_Error
541
+	 * @throws ReflectionException
542
+	 */
543
+	public static function prepareQueryParamsForRestApi(
544
+		array $model_query_params,
545
+		EEM_Base $model,
546
+		?string $requested_version = null
547
+	): array {
548
+		if ($requested_version === null) {
549
+			$requested_version = EED_Core_Rest_Api::latest_rest_api_version();
550
+		}
551
+		$rest_query_params = $model_query_params;
552
+		if (isset($model_query_params[0])) {
553
+			$rest_query_params['where'] = ModelDataTranslator::prepareConditionsQueryParamsForRestApi(
554
+				$model_query_params[0],
555
+				$model,
556
+				$requested_version
557
+			);
558
+			unset($rest_query_params[0]);
559
+		}
560
+		if (isset($model_query_params['having'])) {
561
+			$rest_query_params['having'] = ModelDataTranslator::prepareConditionsQueryParamsForRestApi(
562
+				$model_query_params['having'],
563
+				$model,
564
+				$requested_version
565
+			);
566
+		}
567
+		return apply_filters(
568
+			'FHEE__EventEspresso\core\libraries\rest_api\Model_Data_Translator__prepare_query_params_for_rest_api',
569
+			$rest_query_params,
570
+			$model_query_params,
571
+			$model,
572
+			$requested_version
573
+		);
574
+	}
575
+
576
+
577
+	/**
578
+	 * Prepares all the sub-conditions query parameters (eg having or where conditions) for use in the rest api
579
+	 *
580
+	 * @param array    $inputted_query_params_of_this_type  eg like the "where" or "having" conditions query params
581
+	 * @param EEM_Base $model
582
+	 * @param string   $requested_version                   eg "4.8.36"
583
+	 * @return array ready for use in the rest api query params
584
+	 * @throws EE_Error
585
+	 * @throws RestException if somehow a PHP object were in the query params' values,*@throws
586
+	 * @throws ReflectionException
587
+	 *                                                      ReflectionException
588
+	 *                                                      (which would be really unusual)
589
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
590
+	 */
591
+	public static function prepareConditionsQueryParamsForRestApi(
592
+		array $inputted_query_params_of_this_type,
593
+		EEM_Base $model,
594
+		string $requested_version
595
+	): array {
596
+		$query_param_for_models = [];
597
+		foreach ($inputted_query_params_of_this_type as $query_param_key => $query_param_value) {
598
+			$field = ModelDataTranslator::deduceFieldFromQueryParam(
599
+				ModelDataTranslator::removeStarsAndAnythingAfterFromConditionQueryParamKey($query_param_key),
600
+				$model
601
+			);
602
+			if ($field instanceof EE_Model_Field_Base) {
603
+				// did they specify an operator?
604
+				if (is_array($query_param_value)) {
605
+					$op               = $query_param_value[0];
606
+					$translated_value = [$op];
607
+					if (isset($query_param_value[1])) {
608
+						$value               = $query_param_value[1];
609
+						$translated_value[1] = ModelDataTranslator::prepareFieldValuesForJson(
610
+							$field,
611
+							$value,
612
+							$requested_version
613
+						);
614
+					}
615
+				} else {
616
+					$translated_value = ModelDataTranslator::prepareFieldValueForJson(
617
+						$field,
618
+						$query_param_value,
619
+						$requested_version
620
+					);
621
+				}
622
+				$query_param_for_models[ $query_param_key ] = $translated_value;
623
+			} else {
624
+				// so it's not for a field, assume it's a logic query param key
625
+				$query_param_for_models[ $query_param_key ] =
626
+					ModelDataTranslator::prepareConditionsQueryParamsForRestApi(
627
+						$query_param_value,
628
+						$model,
629
+						$requested_version
630
+					);
631
+			}
632
+		}
633
+		return $query_param_for_models;
634
+	}
635
+
636
+
637
+	/**
638
+	 * @param $condition_query_param_key
639
+	 * @return string
640
+	 */
641
+	public static function removeStarsAndAnythingAfterFromConditionQueryParamKey($condition_query_param_key): string
642
+	{
643
+		$pos_of_star = strpos($condition_query_param_key, '*');
644
+		if ($pos_of_star === false) {
645
+			return $condition_query_param_key;
646
+		}
647
+		return substr($condition_query_param_key, 0, $pos_of_star);
648
+	}
649
+
650
+
651
+	/**
652
+	 * Takes the input parameter and finds the model field that it indicates.
653
+	 *
654
+	 * @param string   $query_param_name like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
655
+	 * @param EEM_Base $model
656
+	 * @return EE_Model_Field_Base
657
+	 * @throws EE_Error
658
+	 * @throws ReflectionException
659
+	 */
660
+	public static function deduceFieldFromQueryParam(string $query_param_name, EEM_Base $model): ?EE_Model_Field_Base
661
+	{
662
+		// ok, now proceed with deducing which part is the model's name, and which is the field's name
663
+		// which will help us find the database table and column
664
+		$query_param_parts = explode('.', $query_param_name);
665
+		if (empty($query_param_parts)) {
666
+			throw new EE_Error(
667
+				sprintf(
668
+					esc_html__(
669
+						'_extract_column_name is empty when trying to extract column and table name from %s',
670
+						'event_espresso'
671
+					),
672
+					$query_param_name
673
+				)
674
+			);
675
+		}
676
+		$number_of_parts       = count($query_param_parts);
677
+		$last_query_param_part = $query_param_parts[ count($query_param_parts) - 1 ];
678
+		$field_name            = $last_query_param_part;
679
+		if ($number_of_parts !== 1) {
680
+			// the last part is the column name, and there are only 2parts. therefore...
681
+			$model = EE_Registry::instance()->load_model($query_param_parts[ $number_of_parts - 2 ]);
682
+		}
683
+		try {
684
+			return $model->field_settings_for($field_name, false);
685
+		} catch (EE_Error $e) {
686
+			return null;
687
+		}
688
+	}
689
+
690
+
691
+	/**
692
+	 * Returns true if $data can be easily represented in JSON.
693
+	 * Basically, objects and resources can't be represented in JSON easily.
694
+	 *
695
+	 * @param mixed $data
696
+	 * @return bool
697
+	 */
698
+	protected static function isRepresentableInJson($data): bool
699
+	{
700
+		return is_scalar($data)
701
+			   || is_array($data)
702
+			   || is_null($data);
703
+	}
704 704
 }
Please login to merge, or discard this patch.
Spacing   +17 added lines, -17 removed lines patch added patch discarded remove patch
@@ -68,7 +68,7 @@  discard block
 block discarded – undo
68 68
         ) {
69 69
             $new_value_maybe_array = [];
70 70
             foreach ($original_value_maybe_array as $array_key => $array_item) {
71
-                $new_value_maybe_array[ $array_key ] = ModelDataTranslator::prepareFieldValueFromJson(
71
+                $new_value_maybe_array[$array_key] = ModelDataTranslator::prepareFieldValueFromJson(
72 72
                     $field_obj,
73 73
                     $array_item,
74 74
                     $requested_version,
@@ -105,7 +105,7 @@  discard block
 block discarded – undo
105 105
         if (is_array($original_value_maybe_array)) {
106 106
             $new_value = [];
107 107
             foreach ($original_value_maybe_array as $key => $value) {
108
-                $new_value[ $key ] = ModelDataTranslator::prepareFieldValuesForJson(
108
+                $new_value[$key] = ModelDataTranslator::prepareFieldValuesForJson(
109 109
                     $field_obj,
110 110
                     $value,
111 111
                     $request_version
@@ -246,7 +246,7 @@  discard block
 block discarded – undo
246 246
                 '0',
247 247
                 STR_PAD_LEFT
248 248
             );
249
-        return $original_timestamp . $offset_sign . $offset_string;
249
+        return $original_timestamp.$offset_sign.$offset_string;
250 250
     }
251 251
 
252 252
 
@@ -328,7 +328,7 @@  discard block
 block discarded – undo
328 328
                     // first, check if its a MySQL timestamp in GMT
329 329
                     $datetime_obj = DateTime::createFromFormat('Y-m-d H:i:s', $original_value);
330 330
                 }
331
-                if (! $datetime_obj instanceof DateTime) {
331
+                if ( ! $datetime_obj instanceof DateTime) {
332 332
                     // so it's not a unix timestamp or a MySQL timestamp. Maybe its in the field's date/time format?
333 333
                     $datetime_obj = $field_obj->prepare_for_set($original_value);
334 334
                 }
@@ -354,7 +354,7 @@  discard block
 block discarded – undo
354 354
                         $original_value,
355 355
                         $field_obj->get_name(),
356 356
                         $field_obj->get_model_name(),
357
-                        $field_obj->get_time_format() . ' ' . $field_obj->get_time_format()
357
+                        $field_obj->get_time_format().' '.$field_obj->get_time_format()
358 358
                     )
359 359
                 );
360 360
             }
@@ -368,7 +368,7 @@  discard block
 block discarded – undo
368 368
         }
369 369
         // are we about to send an object? just don't. We have no good way to represent it in JSON.
370 370
         // can't just check using is_object() because that missed PHP incomplete objects
371
-        if (! ModelDataTranslator::isRepresentableInJson($new_value)) {
371
+        if ( ! ModelDataTranslator::isRepresentableInJson($new_value)) {
372 372
             $new_value = [
373 373
                 'error_code'    => 'php_object_not_return',
374 374
                 'error_message' => esc_html__(
@@ -419,7 +419,7 @@  discard block
 block discarded – undo
419 419
             if ($query_param_meta->getField() instanceof EE_Model_Field_Base) {
420 420
                 $translated_value = $query_param_meta->determineConditionsQueryParameterValue();
421 421
                 if (
422
-                    (isset($query_param_for_models[ $query_param_meta->getQueryParamKey() ])
422
+                    (isset($query_param_for_models[$query_param_meta->getQueryParamKey()])
423 423
                      && $query_param_meta->isGmtField())
424 424
                     || $translated_value === null
425 425
                 ) {
@@ -428,11 +428,11 @@  discard block
 block discarded – undo
428 428
                     // OR we couldn't create a translated value from their input
429 429
                     continue;
430 430
                 }
431
-                $query_param_for_models[ $query_param_meta->getQueryParamKey() ] = $translated_value;
431
+                $query_param_for_models[$query_param_meta->getQueryParamKey()] = $translated_value;
432 432
             } else {
433 433
                 $nested_query_params = $query_param_meta->determineNestedConditionQueryParameters();
434 434
                 if ($nested_query_params) {
435
-                    $query_param_for_models[ $query_param_meta->getQueryParamKey() ] = $nested_query_params;
435
+                    $query_param_for_models[$query_param_meta->getQueryParamKey()] = $nested_query_params;
436 436
                 }
437 437
             }
438 438
         }
@@ -462,10 +462,10 @@  discard block
 block discarded – undo
462 462
      */
463 463
     public static function removeGmtFromFieldName(string $field_name): string
464 464
     {
465
-        if (! ModelDataTranslator::isGmtDateFieldName($field_name)) {
465
+        if ( ! ModelDataTranslator::isGmtDateFieldName($field_name)) {
466 466
             return $field_name;
467 467
         }
468
-        $query_param_sans_stars              =
468
+        $query_param_sans_stars =
469 469
             ModelDataTranslator::removeStarsAndAnythingAfterFromConditionQueryParamKey(
470 470
                 $field_name
471 471
             );
@@ -506,7 +506,7 @@  discard block
 block discarded – undo
506 506
     {
507 507
         $new_array = [];
508 508
         foreach ($field_names as $key => $field_name) {
509
-            $new_array[ $key ] = ModelDataTranslator::prepareFieldNameFromJson($field_name);
509
+            $new_array[$key] = ModelDataTranslator::prepareFieldNameFromJson($field_name);
510 510
         }
511 511
         return $new_array;
512 512
     }
@@ -523,7 +523,7 @@  discard block
 block discarded – undo
523 523
     {
524 524
         $new_array = [];
525 525
         foreach ($field_names_as_keys as $field_name => $value) {
526
-            $new_array[ ModelDataTranslator::prepareFieldNameFromJson($field_name) ] = $value;
526
+            $new_array[ModelDataTranslator::prepareFieldNameFromJson($field_name)] = $value;
527 527
         }
528 528
         return $new_array;
529 529
     }
@@ -619,10 +619,10 @@  discard block
 block discarded – undo
619 619
                         $requested_version
620 620
                     );
621 621
                 }
622
-                $query_param_for_models[ $query_param_key ] = $translated_value;
622
+                $query_param_for_models[$query_param_key] = $translated_value;
623 623
             } else {
624 624
                 // so it's not for a field, assume it's a logic query param key
625
-                $query_param_for_models[ $query_param_key ] =
625
+                $query_param_for_models[$query_param_key] =
626 626
                     ModelDataTranslator::prepareConditionsQueryParamsForRestApi(
627 627
                         $query_param_value,
628 628
                         $model,
@@ -674,11 +674,11 @@  discard block
 block discarded – undo
674 674
             );
675 675
         }
676 676
         $number_of_parts       = count($query_param_parts);
677
-        $last_query_param_part = $query_param_parts[ count($query_param_parts) - 1 ];
677
+        $last_query_param_part = $query_param_parts[count($query_param_parts) - 1];
678 678
         $field_name            = $last_query_param_part;
679 679
         if ($number_of_parts !== 1) {
680 680
             // the last part is the column name, and there are only 2parts. therefore...
681
-            $model = EE_Registry::instance()->load_model($query_param_parts[ $number_of_parts - 2 ]);
681
+            $model = EE_Registry::instance()->load_model($query_param_parts[$number_of_parts - 2]);
682 682
         }
683 683
         try {
684 684
             return $model->field_settings_for($field_name, false);
Please login to merge, or discard this patch.
core/libraries/rest_api/RestException.php 1 patch
Indentation   +54 added lines, -54 removed lines patch added patch discarded remove patch
@@ -17,65 +17,65 @@
 block discarded – undo
17 17
  */
18 18
 class RestException extends EE_Error
19 19
 {
20
-    /**
21
-     * @var array
22
-     */
23
-    protected $wp_error_data = [];
20
+	/**
21
+	 * @var array
22
+	 */
23
+	protected $wp_error_data = [];
24 24
 
25
-    /**
26
-     * @var string
27
-     */
28
-    protected $wp_error_code = '';
25
+	/**
26
+	 * @var string
27
+	 */
28
+	protected $wp_error_code = '';
29 29
 
30 30
 
31
-    /**
32
-     * @param string         $string_code
33
-     * @param string         $message
34
-     * @param array          $wp_error_data
35
-     * @param Exception|null $previous
36
-     */
37
-    public function __construct(
38
-        string $string_code,
39
-        string $message,
40
-        array $wp_error_data = [],
41
-        Exception $previous = null
42
-    ) {
43
-        if (
44
-            is_array($wp_error_data)
45
-            && isset($wp_error_data['status'])
46
-        ) {
47
-            $http_status_number = $wp_error_data['status'];
48
-        } else {
49
-            $http_status_number = 500;
50
-        }
51
-        parent::__construct(
52
-            $message,
53
-            $http_status_number,
54
-            $previous
55
-        );
56
-        $this->wp_error_data = $wp_error_data;
57
-        $this->wp_error_code = $string_code;
58
-    }
31
+	/**
32
+	 * @param string         $string_code
33
+	 * @param string         $message
34
+	 * @param array          $wp_error_data
35
+	 * @param Exception|null $previous
36
+	 */
37
+	public function __construct(
38
+		string $string_code,
39
+		string $message,
40
+		array $wp_error_data = [],
41
+		Exception $previous = null
42
+	) {
43
+		if (
44
+			is_array($wp_error_data)
45
+			&& isset($wp_error_data['status'])
46
+		) {
47
+			$http_status_number = $wp_error_data['status'];
48
+		} else {
49
+			$http_status_number = 500;
50
+		}
51
+		parent::__construct(
52
+			$message,
53
+			$http_status_number,
54
+			$previous
55
+		);
56
+		$this->wp_error_data = $wp_error_data;
57
+		$this->wp_error_code = $string_code;
58
+	}
59 59
 
60 60
 
61
-    /**
62
-     * Array of data that may have been set during the constructor, intended for WP_Error's data
63
-     *
64
-     * @return array
65
-     */
66
-    public function getData(): array
67
-    {
68
-        return $this->wp_error_data;
69
-    }
61
+	/**
62
+	 * Array of data that may have been set during the constructor, intended for WP_Error's data
63
+	 *
64
+	 * @return array
65
+	 */
66
+	public function getData(): array
67
+	{
68
+		return $this->wp_error_data;
69
+	}
70 70
 
71 71
 
72
-    /**
73
-     * Gets the error string
74
-     *
75
-     * @return string
76
-     */
77
-    public function getStringCode(): string
78
-    {
79
-        return $this->wp_error_code;
80
-    }
72
+	/**
73
+	 * Gets the error string
74
+	 *
75
+	 * @return string
76
+	 */
77
+	public function getStringCode(): string
78
+	{
79
+		return $this->wp_error_code;
80
+	}
81 81
 }
Please login to merge, or discard this patch.
core/libraries/rest_api/ModelVersionInfo.php 2 patches
Indentation   +441 added lines, -441 removed lines patch added patch discarded remove patch
@@ -25,445 +25,445 @@
 block discarded – undo
25 25
  */
26 26
 class ModelVersionInfo
27 27
 {
28
-    /**
29
-     * Constant used in the $_model_changes array to indicate that a model
30
-     * was completely new in this version
31
-     */
32
-    const MODEL_ADDED = 'model_added_in_this_version';
33
-
34
-    /**
35
-     * Top-level keys are versions (major and minor version numbers, eg "4.6")
36
-     * next-level keys are model names (eg "Event") that underwent some change in that version
37
-     * and the value is either Model_Version_Info::model_added (indicating the model is completely NEW in this version),
38
-     * or it's an array where the values are model field names,
39
-     * or API resource properties (ie, non-model fields that appear in REST API results)
40
-     * If a version is missing then we don't know anything about what changes it introduced from the previous version
41
-     *
42
-     * @var array
43
-     */
44
-    protected $model_changes = [];
45
-
46
-    /**
47
-     * top-level keys are version numbers,
48
-     * next-level keys are model CLASSNAMES (even parent classnames),
49
-     * and next-level keys are extra resource properties to attach to those models' resources,
50
-     * and next-level key-value pairs, where the keys are:
51
-     * 'raw', 'type', 'nullable', 'table_alias', 'table_column',  'always_available'
52
-     *
53
-     * @var array
54
-     */
55
-    protected $resource_changes = [];
56
-
57
-    /**
58
-     * @var string indicating what version of the API was requested
59
-     * (eg although core might be at version 4.8.11, they may have sent a request
60
-     * for 4.6)
61
-     */
62
-    protected $requested_version = null;
63
-
64
-    /**
65
-     * Keys are model names, values are their classnames.
66
-     * We cache this so we only need to calculate this once per request
67
-     *
68
-     * @var array
69
-     */
70
-    protected $cached_models_for_requested_version = null;
71
-
72
-    /**
73
-     * @var array
74
-     */
75
-    protected $cached_model_changes_between_requested_version_and_current = null;
76
-
77
-    /**
78
-     * @var array
79
-     */
80
-    protected $cached_resource_changes_between_requested_version_and_current = null;
81
-
82
-    /**
83
-     * 2d array where top-level keys are model names, 2nd-level keys are field names
84
-     * and values are the actual field objects
85
-     *
86
-     * @var array
87
-     */
88
-    protected $cached_fields_on_models = [];
89
-
90
-
91
-    /**
92
-     * Model_Version_Info constructor.
93
-     *
94
-     * @param string $requested_version
95
-     */
96
-    public function __construct(string $requested_version)
97
-    {
98
-        $this->requested_version = $requested_version;
99
-        $this->model_changes     = [
100
-            '4.8.29' => [
101
-                // first version where the REST API is in EE core, so no need
102
-                // to specify how its different from the previous
103
-            ],
104
-        ];
105
-        // setup data for "extra" fields added onto resources which don't actually exist on models
106
-        $this->resource_changes = apply_filters(
107
-            'FHEE__Model_Version_Info___construct__extra_resource_properties_for_models',
108
-            []
109
-        );
110
-        $defaults               = [
111
-            'raw'              => false,
112
-            'type'             => 'N/A',
113
-            'nullable'         => true,
114
-            'table_alias'      => 'N/A',
115
-            'table_column'     => 'N/A',
116
-            'always_available' => true,
117
-        ];
118
-        foreach ($this->resource_changes as $model_classnames) {
119
-            foreach ($model_classnames as $model_classname => $extra_fields) {
120
-                foreach ($extra_fields as $field_name => $field_data) {
121
-                    $this->resource_changes[ $model_classname ][ $field_name ]['name'] = $field_name;
122
-                    foreach ($defaults as $attribute => $default_value) {
123
-                        if (! isset($this->resource_changes[ $model_classname ][ $field_name ][ $attribute ])) {
124
-                            $this->resource_changes[ $model_classname ][ $field_name ][ $attribute ] = $default_value;
125
-                        }
126
-                    }
127
-                }
128
-            }
129
-        }
130
-    }
131
-
132
-
133
-    /**
134
-     * Returns a slice of Model_Version_Info::model_changes()'s array
135
-     * indicating exactly what changes happened between the current core version,
136
-     * and the version requested
137
-     *
138
-     * @return array
139
-     */
140
-    public function modelChangesBetweenRequestedVersionAndCurrent(): array
141
-    {
142
-        if ($this->cached_model_changes_between_requested_version_and_current === null) {
143
-            $model_changes = [];
144
-            foreach ($this->modelChanges() as $version => $models_changed_in_version) {
145
-                if ($version <= EED_Core_Rest_Api::core_version() && $version > $this->requestedVersion()) {
146
-                    $model_changes[ $version ] = $models_changed_in_version;
147
-                }
148
-            }
149
-            $this->cached_model_changes_between_requested_version_and_current = $model_changes;
150
-        }
151
-        return $this->cached_model_changes_between_requested_version_and_current;
152
-    }
153
-
154
-
155
-    /**
156
-     * Returns a slice of Model_Version_Info::model_changes()'s array
157
-     * indicating exactly what changes happened between the current core version,
158
-     * and the version requested
159
-     *
160
-     * @return array
161
-     */
162
-    public function resourceChangesBetweenRequestedVersionAndCurrent(): array
163
-    {
164
-        if ($this->cached_resource_changes_between_requested_version_and_current === null) {
165
-            $resource_changes = [];
166
-            foreach ($this->resourceChanges() as $version => $model_classnames) {
167
-                if ($version <= EED_Core_Rest_Api::core_version() && $version > $this->requestedVersion()) {
168
-                    $resource_changes[ $version ] = $model_classnames;
169
-                }
170
-            }
171
-            $this->cached_resource_changes_between_requested_version_and_current = $resource_changes;
172
-        }
173
-        return $this->cached_resource_changes_between_requested_version_and_current;
174
-    }
175
-
176
-
177
-    /**
178
-     * If a request was sent to 'wp-json/ee/v4.7/events' this would be '4.7'
179
-     *
180
-     * @return string like '4.6'
181
-     */
182
-    public function requestedVersion(): ?string
183
-    {
184
-        return $this->requested_version;
185
-    }
186
-
187
-
188
-    /**
189
-     * Returns an array describing how the models have changed in each version of core
190
-     * that supports the API (starting at 4.6)
191
-     * Top-level keys are versions (major and minor version numbers, eg "4.6")
192
-     * next-level keys are model names (eg "Event") that underwent some change in that version
193
-     * and the value is either NULL (indicating the model is completely NEW in this version),
194
-     * or it's an array where fields are value names.
195
-     * If a version is missing then we don't know anything about what changes it introduced from the previous version
196
-     *
197
-     * @return array
198
-     */
199
-    public function modelChanges(): array
200
-    {
201
-        return $this->model_changes;
202
-    }
203
-
204
-
205
-    /**
206
-     * Takes into account the requested version, and the current version, and
207
-     * what changed between the two, and tries to return.
208
-     * Analogous to EE_Registry::instance()->non_abstract_db_models
209
-     *
210
-     * @return array keys are model names, values are their classname
211
-     */
212
-    public function modelsForRequestedVersion(): array
213
-    {
214
-        if ($this->cached_models_for_requested_version === null) {
215
-            $all_models_in_current_version = EE_Registry::instance()->non_abstract_db_models;
216
-            foreach ($this->modelChangesBetweenRequestedVersionAndCurrent() as $models_changed) {
217
-                foreach ($models_changed as $model_name => $new_indicator_or_fields_added) {
218
-                    if ($new_indicator_or_fields_added === ModelVersionInfo::MODEL_ADDED) {
219
-                        unset($all_models_in_current_version[ $model_name ]);
220
-                    }
221
-                }
222
-            }
223
-            $this->cached_models_for_requested_version = apply_filters(
224
-                'FHEE__EventEspresso_core_libraries_rest_api__models_for_requested_version',
225
-                $all_models_in_current_version,
226
-                $this
227
-            );
228
-        }
229
-        return $this->cached_models_for_requested_version;
230
-    }
231
-
232
-
233
-    /**
234
-     * Determines if this is a valid model name in the requested version.
235
-     * Similar to EE_Registry::instance()->is_model_name(), but takes the requested
236
-     * version's models into account
237
-     *
238
-     * @param string $model_name eg 'Event'
239
-     * @return boolean
240
-     */
241
-    public function isModelNameInThisVersion(string $model_name): bool
242
-    {
243
-        $model_names = $this->modelsForRequestedVersion();
244
-        if (isset($model_names[ $model_name ])) {
245
-            return true;
246
-        } else {
247
-            return false;
248
-        }
249
-    }
250
-
251
-
252
-    /**
253
-     * Wrapper for EE_Registry::instance()->load_model(), but takes the requested
254
-     * version's models into account
255
-     *
256
-     * @param string $model_name
257
-     * @return EEM_Base
258
-     * @throws EE_Error
259
-     * @throws ReflectionException
260
-     */
261
-    public function loadModel(string $model_name): EEM_Base
262
-    {
263
-        if ($this->isModelNameInThisVersion($model_name)) {
264
-            return EE_Registry::instance()->load_model($model_name);
265
-        } else {
266
-            throw new EE_Error(
267
-                sprintf(
268
-                    esc_html__(
269
-                        'Cannot load model "%1$s" because it does not exist in version %2$s of Event Espresso',
270
-                        'event_espresso'
271
-                    ),
272
-                    $model_name,
273
-                    $this->requestedVersion()
274
-                )
275
-            );
276
-        }
277
-    }
278
-
279
-
280
-    /**
281
-     * Gets all the fields that should exist on this model right now
282
-     *
283
-     * @param EEM_Base $model
284
-     * @return array|EE_Model_Field_Base[]
285
-     */
286
-    public function fieldsOnModelInThisVersion(EEM_Base $model): array
287
-    {
288
-        if (! isset($this->cached_fields_on_models[ $model->get_this_model_name() ])) {
289
-            // get all model changes between the requested version and current core version
290
-            $changes = $this->modelChangesBetweenRequestedVersionAndCurrent();
291
-            // fetch all fields currently on this model
292
-            $current_fields = $model->field_settings();
293
-            // remove all fields that have been added since
294
-            foreach ($changes as $changes_in_version) {
295
-                if (
296
-                    isset($changes_in_version[ $model->get_this_model_name() ])
297
-                    && $changes_in_version[ $model->get_this_model_name() ] !== ModelVersionInfo::MODEL_ADDED
298
-                ) {
299
-                    $current_fields = array_diff_key(
300
-                        $current_fields,
301
-                        array_flip($changes_in_version[ $model->get_this_model_name() ])
302
-                    );
303
-                }
304
-            }
305
-            $this->cached_fields_on_models = $current_fields;
306
-        }
307
-        return $this->cached_fields_on_models;
308
-    }
309
-
310
-
311
-    /**
312
-     * Determines if $object is of one of the classes of $classes. Similar to
313
-     * in_array(), except this checks if $object is a subclass of the classnames provided
314
-     * in $classnames
315
-     *
316
-     * @param object $object
317
-     * @param array  $classnames
318
-     * @return boolean
319
-     */
320
-    public function isSubclassOfOne($object, array $classnames): bool
321
-    {
322
-        foreach ($classnames as $classname) {
323
-            if (is_a($object, $classname)) {
324
-                return true;
325
-            }
326
-        }
327
-        return false;
328
-    }
329
-
330
-
331
-    /**
332
-     * Returns the list of model field classes that that the API basically ignores
333
-     *
334
-     * @return array
335
-     */
336
-    public function fieldsIgnored(): array
337
-    {
338
-        return apply_filters(
339
-            'FHEE__Controller_Model_Read_fields_ignored',
340
-            []
341
-        );
342
-    }
343
-
344
-
345
-    /**
346
-     * If this field one that should be ignored by the API?
347
-     *
348
-     * @param EE_Model_Field_Base
349
-     * @return boolean
350
-     */
351
-    public function fieldIsIgnored($field_obj): bool
352
-    {
353
-        return $this->isSubclassOfOne($field_obj, $this->fieldsIgnored());
354
-    }
355
-
356
-
357
-    /**
358
-     * Returns the list of model field classes that have a "raw" and non-raw formats.
359
-     * Normally the "raw" versions are only accessible to those who can edit them.
360
-     *
361
-     * @return array an array of EE_Model_Field_Base child classnames
362
-     */
363
-    public function fieldsThatHaveRenderedFormat(): array
364
-    {
365
-        return apply_filters(
366
-            'FHEE__Controller_Model_Read__fields_raw',
367
-            ['EE_Post_Content_Field', 'EE_Full_HTML_Field']
368
-        );
369
-    }
370
-
371
-
372
-    /**
373
-     * If this field one that has a raw format
374
-     *
375
-     * @param EE_Model_Field_Base
376
-     * @return boolean
377
-     */
378
-    public function fieldHasRenderedFormat($field_obj): bool
379
-    {
380
-        return $this->isSubclassOfOne($field_obj, $this->fieldsThatHaveRenderedFormat());
381
-    }
382
-
383
-
384
-    /**
385
-     * Returns the list of model field classes that have a "_pretty" and non-pretty versions.
386
-     * The pretty version of the field is NOT query-able or editable, but requires no extra permissions
387
-     * to view
388
-     *
389
-     * @return array an array of EE_Model_Field_Base child classnames
390
-     */
391
-    public function fieldsThatHavePrettyFormat(): array
392
-    {
393
-        return apply_filters(
394
-            'FHEE__Controller_Model_Read__fields_pretty',
395
-            ['EE_Enum_Integer_Field', 'EE_Enum_Text_Field', 'EE_Money_Field']
396
-        );
397
-    }
398
-
399
-
400
-    /**
401
-     * If this field one that has a pretty equivalent
402
-     *
403
-     * @param EE_Model_Field_Base
404
-     * @return boolean
405
-     */
406
-    public function fieldHasPrettyFormat($field_obj): bool
407
-    {
408
-        return $this->isSubclassOfOne($field_obj, $this->fieldsThatHavePrettyFormat());
409
-    }
410
-
411
-
412
-    /**
413
-     * Returns an array describing what extra API resource properties have been added through the versions
414
-     *
415
-     * @return array
416
-     * @see _extra_resource_properties_for_models()
417
-     */
418
-    public function resourceChanges(): array
419
-    {
420
-        return $this->resource_changes;
421
-    }
422
-
423
-
424
-    /**
425
-     * Returns an array where keys are extra resource properties in this version of the API,
426
-     * and values are key-value pairs describing the new properties.
427
-     *
428
-     * @param EEM_Base $model
429
-     * @return array
430
-     * @see Model_Version::_resource_changes
431
-     *
432
-     */
433
-    public function extraResourcePropertiesForModel(EEM_Base $model): array
434
-    {
435
-        $extra_properties = [];
436
-        foreach ($this->resourceChangesBetweenRequestedVersionAndCurrent() as $model_classnames) {
437
-            foreach ($model_classnames as $model_classname => $properties_added_in_this_version) {
438
-                if (is_subclass_of($model, $model_classname)) {
439
-                    $extra_properties = array_merge($extra_properties, $properties_added_in_this_version);
440
-                }
441
-            }
442
-        }
443
-        return $extra_properties;
444
-    }
445
-
446
-
447
-    /**
448
-     * Gets all the related models for the specified model. It's good to use this
449
-     * in case this model didn't exist for this version or something
450
-     *
451
-     * @param EEM_Base $model
452
-     * @return EE_Model_Relation_Base[]
453
-     */
454
-    public function relationSettings(EEM_Base $model): array
455
-    {
456
-        $relations = [];
457
-        foreach ($model->relation_settings() as $relation_name => $relation_obj) {
458
-            if ($this->isModelNameInThisVersion($relation_name)) {
459
-                $relations[ $relation_name ] = $relation_obj;
460
-            }
461
-        }
462
-        // filter the results, but use the old filter name
463
-        return apply_filters(
464
-            'FHEE__Read__create_entity_from_wpdb_result__related_models_to_include',
465
-            $relations,
466
-            $model
467
-        );
468
-    }
28
+	/**
29
+	 * Constant used in the $_model_changes array to indicate that a model
30
+	 * was completely new in this version
31
+	 */
32
+	const MODEL_ADDED = 'model_added_in_this_version';
33
+
34
+	/**
35
+	 * Top-level keys are versions (major and minor version numbers, eg "4.6")
36
+	 * next-level keys are model names (eg "Event") that underwent some change in that version
37
+	 * and the value is either Model_Version_Info::model_added (indicating the model is completely NEW in this version),
38
+	 * or it's an array where the values are model field names,
39
+	 * or API resource properties (ie, non-model fields that appear in REST API results)
40
+	 * If a version is missing then we don't know anything about what changes it introduced from the previous version
41
+	 *
42
+	 * @var array
43
+	 */
44
+	protected $model_changes = [];
45
+
46
+	/**
47
+	 * top-level keys are version numbers,
48
+	 * next-level keys are model CLASSNAMES (even parent classnames),
49
+	 * and next-level keys are extra resource properties to attach to those models' resources,
50
+	 * and next-level key-value pairs, where the keys are:
51
+	 * 'raw', 'type', 'nullable', 'table_alias', 'table_column',  'always_available'
52
+	 *
53
+	 * @var array
54
+	 */
55
+	protected $resource_changes = [];
56
+
57
+	/**
58
+	 * @var string indicating what version of the API was requested
59
+	 * (eg although core might be at version 4.8.11, they may have sent a request
60
+	 * for 4.6)
61
+	 */
62
+	protected $requested_version = null;
63
+
64
+	/**
65
+	 * Keys are model names, values are their classnames.
66
+	 * We cache this so we only need to calculate this once per request
67
+	 *
68
+	 * @var array
69
+	 */
70
+	protected $cached_models_for_requested_version = null;
71
+
72
+	/**
73
+	 * @var array
74
+	 */
75
+	protected $cached_model_changes_between_requested_version_and_current = null;
76
+
77
+	/**
78
+	 * @var array
79
+	 */
80
+	protected $cached_resource_changes_between_requested_version_and_current = null;
81
+
82
+	/**
83
+	 * 2d array where top-level keys are model names, 2nd-level keys are field names
84
+	 * and values are the actual field objects
85
+	 *
86
+	 * @var array
87
+	 */
88
+	protected $cached_fields_on_models = [];
89
+
90
+
91
+	/**
92
+	 * Model_Version_Info constructor.
93
+	 *
94
+	 * @param string $requested_version
95
+	 */
96
+	public function __construct(string $requested_version)
97
+	{
98
+		$this->requested_version = $requested_version;
99
+		$this->model_changes     = [
100
+			'4.8.29' => [
101
+				// first version where the REST API is in EE core, so no need
102
+				// to specify how its different from the previous
103
+			],
104
+		];
105
+		// setup data for "extra" fields added onto resources which don't actually exist on models
106
+		$this->resource_changes = apply_filters(
107
+			'FHEE__Model_Version_Info___construct__extra_resource_properties_for_models',
108
+			[]
109
+		);
110
+		$defaults               = [
111
+			'raw'              => false,
112
+			'type'             => 'N/A',
113
+			'nullable'         => true,
114
+			'table_alias'      => 'N/A',
115
+			'table_column'     => 'N/A',
116
+			'always_available' => true,
117
+		];
118
+		foreach ($this->resource_changes as $model_classnames) {
119
+			foreach ($model_classnames as $model_classname => $extra_fields) {
120
+				foreach ($extra_fields as $field_name => $field_data) {
121
+					$this->resource_changes[ $model_classname ][ $field_name ]['name'] = $field_name;
122
+					foreach ($defaults as $attribute => $default_value) {
123
+						if (! isset($this->resource_changes[ $model_classname ][ $field_name ][ $attribute ])) {
124
+							$this->resource_changes[ $model_classname ][ $field_name ][ $attribute ] = $default_value;
125
+						}
126
+					}
127
+				}
128
+			}
129
+		}
130
+	}
131
+
132
+
133
+	/**
134
+	 * Returns a slice of Model_Version_Info::model_changes()'s array
135
+	 * indicating exactly what changes happened between the current core version,
136
+	 * and the version requested
137
+	 *
138
+	 * @return array
139
+	 */
140
+	public function modelChangesBetweenRequestedVersionAndCurrent(): array
141
+	{
142
+		if ($this->cached_model_changes_between_requested_version_and_current === null) {
143
+			$model_changes = [];
144
+			foreach ($this->modelChanges() as $version => $models_changed_in_version) {
145
+				if ($version <= EED_Core_Rest_Api::core_version() && $version > $this->requestedVersion()) {
146
+					$model_changes[ $version ] = $models_changed_in_version;
147
+				}
148
+			}
149
+			$this->cached_model_changes_between_requested_version_and_current = $model_changes;
150
+		}
151
+		return $this->cached_model_changes_between_requested_version_and_current;
152
+	}
153
+
154
+
155
+	/**
156
+	 * Returns a slice of Model_Version_Info::model_changes()'s array
157
+	 * indicating exactly what changes happened between the current core version,
158
+	 * and the version requested
159
+	 *
160
+	 * @return array
161
+	 */
162
+	public function resourceChangesBetweenRequestedVersionAndCurrent(): array
163
+	{
164
+		if ($this->cached_resource_changes_between_requested_version_and_current === null) {
165
+			$resource_changes = [];
166
+			foreach ($this->resourceChanges() as $version => $model_classnames) {
167
+				if ($version <= EED_Core_Rest_Api::core_version() && $version > $this->requestedVersion()) {
168
+					$resource_changes[ $version ] = $model_classnames;
169
+				}
170
+			}
171
+			$this->cached_resource_changes_between_requested_version_and_current = $resource_changes;
172
+		}
173
+		return $this->cached_resource_changes_between_requested_version_and_current;
174
+	}
175
+
176
+
177
+	/**
178
+	 * If a request was sent to 'wp-json/ee/v4.7/events' this would be '4.7'
179
+	 *
180
+	 * @return string like '4.6'
181
+	 */
182
+	public function requestedVersion(): ?string
183
+	{
184
+		return $this->requested_version;
185
+	}
186
+
187
+
188
+	/**
189
+	 * Returns an array describing how the models have changed in each version of core
190
+	 * that supports the API (starting at 4.6)
191
+	 * Top-level keys are versions (major and minor version numbers, eg "4.6")
192
+	 * next-level keys are model names (eg "Event") that underwent some change in that version
193
+	 * and the value is either NULL (indicating the model is completely NEW in this version),
194
+	 * or it's an array where fields are value names.
195
+	 * If a version is missing then we don't know anything about what changes it introduced from the previous version
196
+	 *
197
+	 * @return array
198
+	 */
199
+	public function modelChanges(): array
200
+	{
201
+		return $this->model_changes;
202
+	}
203
+
204
+
205
+	/**
206
+	 * Takes into account the requested version, and the current version, and
207
+	 * what changed between the two, and tries to return.
208
+	 * Analogous to EE_Registry::instance()->non_abstract_db_models
209
+	 *
210
+	 * @return array keys are model names, values are their classname
211
+	 */
212
+	public function modelsForRequestedVersion(): array
213
+	{
214
+		if ($this->cached_models_for_requested_version === null) {
215
+			$all_models_in_current_version = EE_Registry::instance()->non_abstract_db_models;
216
+			foreach ($this->modelChangesBetweenRequestedVersionAndCurrent() as $models_changed) {
217
+				foreach ($models_changed as $model_name => $new_indicator_or_fields_added) {
218
+					if ($new_indicator_or_fields_added === ModelVersionInfo::MODEL_ADDED) {
219
+						unset($all_models_in_current_version[ $model_name ]);
220
+					}
221
+				}
222
+			}
223
+			$this->cached_models_for_requested_version = apply_filters(
224
+				'FHEE__EventEspresso_core_libraries_rest_api__models_for_requested_version',
225
+				$all_models_in_current_version,
226
+				$this
227
+			);
228
+		}
229
+		return $this->cached_models_for_requested_version;
230
+	}
231
+
232
+
233
+	/**
234
+	 * Determines if this is a valid model name in the requested version.
235
+	 * Similar to EE_Registry::instance()->is_model_name(), but takes the requested
236
+	 * version's models into account
237
+	 *
238
+	 * @param string $model_name eg 'Event'
239
+	 * @return boolean
240
+	 */
241
+	public function isModelNameInThisVersion(string $model_name): bool
242
+	{
243
+		$model_names = $this->modelsForRequestedVersion();
244
+		if (isset($model_names[ $model_name ])) {
245
+			return true;
246
+		} else {
247
+			return false;
248
+		}
249
+	}
250
+
251
+
252
+	/**
253
+	 * Wrapper for EE_Registry::instance()->load_model(), but takes the requested
254
+	 * version's models into account
255
+	 *
256
+	 * @param string $model_name
257
+	 * @return EEM_Base
258
+	 * @throws EE_Error
259
+	 * @throws ReflectionException
260
+	 */
261
+	public function loadModel(string $model_name): EEM_Base
262
+	{
263
+		if ($this->isModelNameInThisVersion($model_name)) {
264
+			return EE_Registry::instance()->load_model($model_name);
265
+		} else {
266
+			throw new EE_Error(
267
+				sprintf(
268
+					esc_html__(
269
+						'Cannot load model "%1$s" because it does not exist in version %2$s of Event Espresso',
270
+						'event_espresso'
271
+					),
272
+					$model_name,
273
+					$this->requestedVersion()
274
+				)
275
+			);
276
+		}
277
+	}
278
+
279
+
280
+	/**
281
+	 * Gets all the fields that should exist on this model right now
282
+	 *
283
+	 * @param EEM_Base $model
284
+	 * @return array|EE_Model_Field_Base[]
285
+	 */
286
+	public function fieldsOnModelInThisVersion(EEM_Base $model): array
287
+	{
288
+		if (! isset($this->cached_fields_on_models[ $model->get_this_model_name() ])) {
289
+			// get all model changes between the requested version and current core version
290
+			$changes = $this->modelChangesBetweenRequestedVersionAndCurrent();
291
+			// fetch all fields currently on this model
292
+			$current_fields = $model->field_settings();
293
+			// remove all fields that have been added since
294
+			foreach ($changes as $changes_in_version) {
295
+				if (
296
+					isset($changes_in_version[ $model->get_this_model_name() ])
297
+					&& $changes_in_version[ $model->get_this_model_name() ] !== ModelVersionInfo::MODEL_ADDED
298
+				) {
299
+					$current_fields = array_diff_key(
300
+						$current_fields,
301
+						array_flip($changes_in_version[ $model->get_this_model_name() ])
302
+					);
303
+				}
304
+			}
305
+			$this->cached_fields_on_models = $current_fields;
306
+		}
307
+		return $this->cached_fields_on_models;
308
+	}
309
+
310
+
311
+	/**
312
+	 * Determines if $object is of one of the classes of $classes. Similar to
313
+	 * in_array(), except this checks if $object is a subclass of the classnames provided
314
+	 * in $classnames
315
+	 *
316
+	 * @param object $object
317
+	 * @param array  $classnames
318
+	 * @return boolean
319
+	 */
320
+	public function isSubclassOfOne($object, array $classnames): bool
321
+	{
322
+		foreach ($classnames as $classname) {
323
+			if (is_a($object, $classname)) {
324
+				return true;
325
+			}
326
+		}
327
+		return false;
328
+	}
329
+
330
+
331
+	/**
332
+	 * Returns the list of model field classes that that the API basically ignores
333
+	 *
334
+	 * @return array
335
+	 */
336
+	public function fieldsIgnored(): array
337
+	{
338
+		return apply_filters(
339
+			'FHEE__Controller_Model_Read_fields_ignored',
340
+			[]
341
+		);
342
+	}
343
+
344
+
345
+	/**
346
+	 * If this field one that should be ignored by the API?
347
+	 *
348
+	 * @param EE_Model_Field_Base
349
+	 * @return boolean
350
+	 */
351
+	public function fieldIsIgnored($field_obj): bool
352
+	{
353
+		return $this->isSubclassOfOne($field_obj, $this->fieldsIgnored());
354
+	}
355
+
356
+
357
+	/**
358
+	 * Returns the list of model field classes that have a "raw" and non-raw formats.
359
+	 * Normally the "raw" versions are only accessible to those who can edit them.
360
+	 *
361
+	 * @return array an array of EE_Model_Field_Base child classnames
362
+	 */
363
+	public function fieldsThatHaveRenderedFormat(): array
364
+	{
365
+		return apply_filters(
366
+			'FHEE__Controller_Model_Read__fields_raw',
367
+			['EE_Post_Content_Field', 'EE_Full_HTML_Field']
368
+		);
369
+	}
370
+
371
+
372
+	/**
373
+	 * If this field one that has a raw format
374
+	 *
375
+	 * @param EE_Model_Field_Base
376
+	 * @return boolean
377
+	 */
378
+	public function fieldHasRenderedFormat($field_obj): bool
379
+	{
380
+		return $this->isSubclassOfOne($field_obj, $this->fieldsThatHaveRenderedFormat());
381
+	}
382
+
383
+
384
+	/**
385
+	 * Returns the list of model field classes that have a "_pretty" and non-pretty versions.
386
+	 * The pretty version of the field is NOT query-able or editable, but requires no extra permissions
387
+	 * to view
388
+	 *
389
+	 * @return array an array of EE_Model_Field_Base child classnames
390
+	 */
391
+	public function fieldsThatHavePrettyFormat(): array
392
+	{
393
+		return apply_filters(
394
+			'FHEE__Controller_Model_Read__fields_pretty',
395
+			['EE_Enum_Integer_Field', 'EE_Enum_Text_Field', 'EE_Money_Field']
396
+		);
397
+	}
398
+
399
+
400
+	/**
401
+	 * If this field one that has a pretty equivalent
402
+	 *
403
+	 * @param EE_Model_Field_Base
404
+	 * @return boolean
405
+	 */
406
+	public function fieldHasPrettyFormat($field_obj): bool
407
+	{
408
+		return $this->isSubclassOfOne($field_obj, $this->fieldsThatHavePrettyFormat());
409
+	}
410
+
411
+
412
+	/**
413
+	 * Returns an array describing what extra API resource properties have been added through the versions
414
+	 *
415
+	 * @return array
416
+	 * @see _extra_resource_properties_for_models()
417
+	 */
418
+	public function resourceChanges(): array
419
+	{
420
+		return $this->resource_changes;
421
+	}
422
+
423
+
424
+	/**
425
+	 * Returns an array where keys are extra resource properties in this version of the API,
426
+	 * and values are key-value pairs describing the new properties.
427
+	 *
428
+	 * @param EEM_Base $model
429
+	 * @return array
430
+	 * @see Model_Version::_resource_changes
431
+	 *
432
+	 */
433
+	public function extraResourcePropertiesForModel(EEM_Base $model): array
434
+	{
435
+		$extra_properties = [];
436
+		foreach ($this->resourceChangesBetweenRequestedVersionAndCurrent() as $model_classnames) {
437
+			foreach ($model_classnames as $model_classname => $properties_added_in_this_version) {
438
+				if (is_subclass_of($model, $model_classname)) {
439
+					$extra_properties = array_merge($extra_properties, $properties_added_in_this_version);
440
+				}
441
+			}
442
+		}
443
+		return $extra_properties;
444
+	}
445
+
446
+
447
+	/**
448
+	 * Gets all the related models for the specified model. It's good to use this
449
+	 * in case this model didn't exist for this version or something
450
+	 *
451
+	 * @param EEM_Base $model
452
+	 * @return EE_Model_Relation_Base[]
453
+	 */
454
+	public function relationSettings(EEM_Base $model): array
455
+	{
456
+		$relations = [];
457
+		foreach ($model->relation_settings() as $relation_name => $relation_obj) {
458
+			if ($this->isModelNameInThisVersion($relation_name)) {
459
+				$relations[ $relation_name ] = $relation_obj;
460
+			}
461
+		}
462
+		// filter the results, but use the old filter name
463
+		return apply_filters(
464
+			'FHEE__Read__create_entity_from_wpdb_result__related_models_to_include',
465
+			$relations,
466
+			$model
467
+		);
468
+	}
469 469
 }
Please login to merge, or discard this patch.
Spacing   +13 added lines, -13 removed lines patch added patch discarded remove patch
@@ -107,7 +107,7 @@  discard block
 block discarded – undo
107 107
             'FHEE__Model_Version_Info___construct__extra_resource_properties_for_models',
108 108
             []
109 109
         );
110
-        $defaults               = [
110
+        $defaults = [
111 111
             'raw'              => false,
112 112
             'type'             => 'N/A',
113 113
             'nullable'         => true,
@@ -118,10 +118,10 @@  discard block
 block discarded – undo
118 118
         foreach ($this->resource_changes as $model_classnames) {
119 119
             foreach ($model_classnames as $model_classname => $extra_fields) {
120 120
                 foreach ($extra_fields as $field_name => $field_data) {
121
-                    $this->resource_changes[ $model_classname ][ $field_name ]['name'] = $field_name;
121
+                    $this->resource_changes[$model_classname][$field_name]['name'] = $field_name;
122 122
                     foreach ($defaults as $attribute => $default_value) {
123
-                        if (! isset($this->resource_changes[ $model_classname ][ $field_name ][ $attribute ])) {
124
-                            $this->resource_changes[ $model_classname ][ $field_name ][ $attribute ] = $default_value;
123
+                        if ( ! isset($this->resource_changes[$model_classname][$field_name][$attribute])) {
124
+                            $this->resource_changes[$model_classname][$field_name][$attribute] = $default_value;
125 125
                         }
126 126
                     }
127 127
                 }
@@ -143,7 +143,7 @@  discard block
 block discarded – undo
143 143
             $model_changes = [];
144 144
             foreach ($this->modelChanges() as $version => $models_changed_in_version) {
145 145
                 if ($version <= EED_Core_Rest_Api::core_version() && $version > $this->requestedVersion()) {
146
-                    $model_changes[ $version ] = $models_changed_in_version;
146
+                    $model_changes[$version] = $models_changed_in_version;
147 147
                 }
148 148
             }
149 149
             $this->cached_model_changes_between_requested_version_and_current = $model_changes;
@@ -165,7 +165,7 @@  discard block
 block discarded – undo
165 165
             $resource_changes = [];
166 166
             foreach ($this->resourceChanges() as $version => $model_classnames) {
167 167
                 if ($version <= EED_Core_Rest_Api::core_version() && $version > $this->requestedVersion()) {
168
-                    $resource_changes[ $version ] = $model_classnames;
168
+                    $resource_changes[$version] = $model_classnames;
169 169
                 }
170 170
             }
171 171
             $this->cached_resource_changes_between_requested_version_and_current = $resource_changes;
@@ -216,7 +216,7 @@  discard block
 block discarded – undo
216 216
             foreach ($this->modelChangesBetweenRequestedVersionAndCurrent() as $models_changed) {
217 217
                 foreach ($models_changed as $model_name => $new_indicator_or_fields_added) {
218 218
                     if ($new_indicator_or_fields_added === ModelVersionInfo::MODEL_ADDED) {
219
-                        unset($all_models_in_current_version[ $model_name ]);
219
+                        unset($all_models_in_current_version[$model_name]);
220 220
                     }
221 221
                 }
222 222
             }
@@ -241,7 +241,7 @@  discard block
 block discarded – undo
241 241
     public function isModelNameInThisVersion(string $model_name): bool
242 242
     {
243 243
         $model_names = $this->modelsForRequestedVersion();
244
-        if (isset($model_names[ $model_name ])) {
244
+        if (isset($model_names[$model_name])) {
245 245
             return true;
246 246
         } else {
247 247
             return false;
@@ -285,7 +285,7 @@  discard block
 block discarded – undo
285 285
      */
286 286
     public function fieldsOnModelInThisVersion(EEM_Base $model): array
287 287
     {
288
-        if (! isset($this->cached_fields_on_models[ $model->get_this_model_name() ])) {
288
+        if ( ! isset($this->cached_fields_on_models[$model->get_this_model_name()])) {
289 289
             // get all model changes between the requested version and current core version
290 290
             $changes = $this->modelChangesBetweenRequestedVersionAndCurrent();
291 291
             // fetch all fields currently on this model
@@ -293,12 +293,12 @@  discard block
 block discarded – undo
293 293
             // remove all fields that have been added since
294 294
             foreach ($changes as $changes_in_version) {
295 295
                 if (
296
-                    isset($changes_in_version[ $model->get_this_model_name() ])
297
-                    && $changes_in_version[ $model->get_this_model_name() ] !== ModelVersionInfo::MODEL_ADDED
296
+                    isset($changes_in_version[$model->get_this_model_name()])
297
+                    && $changes_in_version[$model->get_this_model_name()] !== ModelVersionInfo::MODEL_ADDED
298 298
                 ) {
299 299
                     $current_fields = array_diff_key(
300 300
                         $current_fields,
301
-                        array_flip($changes_in_version[ $model->get_this_model_name() ])
301
+                        array_flip($changes_in_version[$model->get_this_model_name()])
302 302
                     );
303 303
                 }
304 304
             }
@@ -456,7 +456,7 @@  discard block
 block discarded – undo
456 456
         $relations = [];
457 457
         foreach ($model->relation_settings() as $relation_name => $relation_obj) {
458 458
             if ($this->isModelNameInThisVersion($relation_name)) {
459
-                $relations[ $relation_name ] = $relation_obj;
459
+                $relations[$relation_name] = $relation_obj;
460 460
             }
461 461
         }
462 462
         // filter the results, but use the old filter name
Please login to merge, or discard this patch.
core/libraries/rest_api/controllers/model/Write.php 2 patches
Indentation   +651 added lines, -651 removed lines patch added patch discarded remove patch
@@ -37,655 +37,655 @@
 block discarded – undo
37 37
  */
38 38
 class Write extends Base
39 39
 {
40
-    /**
41
-     * @throws EE_Error
42
-     * @throws ReflectionException
43
-     */
44
-    public function __construct()
45
-    {
46
-        parent::__construct();
47
-        EE_Registry::instance()->load_helper('Inflector');
48
-    }
49
-
50
-
51
-    /**
52
-     * @param string $version
53
-     * @return Write
54
-     * @since $VID:$
55
-     */
56
-    public static function getModelWriteController(string $version): Write
57
-    {
58
-        /** @var Write $controller */
59
-        $controller = LoaderFactory::getLoader()->getNew(Write::class);
60
-        $controller->setRequestedVersion($version);
61
-        return $controller;
62
-    }
63
-
64
-
65
-    /**
66
-     * Handles requests to get all (or a filtered subset) of entities for a particular model
67
-     *
68
-     * @param WP_REST_Request $request
69
-     * @param string          $version
70
-     * @param string          $model_name
71
-     * @return WP_REST_Response
72
-     */
73
-    public static function handleRequestInsert(
74
-        WP_REST_Request $request,
75
-        string $version,
76
-        string $model_name
77
-    ): WP_REST_Response {
78
-        $controller = Write::getModelWriteController($version);
79
-        try {
80
-            return $controller->sendResponse(
81
-                $controller->insert(
82
-                    $controller->getModelVersionInfo()->loadModel($model_name),
83
-                    $request
84
-                )
85
-            );
86
-        } catch (Exception $e) {
87
-            return $controller->sendResponse($e);
88
-        }
89
-    }
90
-
91
-
92
-    /**
93
-     * Handles a request from \WP_REST_Server to update an EE model
94
-     *
95
-     * @param WP_REST_Request $request
96
-     * @param string          $version
97
-     * @param string          $model_name
98
-     * @return WP_REST_Response
99
-     */
100
-    public static function handleRequestUpdate(
101
-        WP_REST_Request $request,
102
-        string $version,
103
-        string $model_name
104
-    ): WP_REST_Response {
105
-        $controller = Write::getModelWriteController($version);
106
-        try {
107
-            return $controller->sendResponse(
108
-                $controller->update(
109
-                    $controller->getModelVersionInfo()->loadModel($model_name),
110
-                    $request
111
-                )
112
-            );
113
-        } catch (Exception $e) {
114
-            return $controller->sendResponse($e);
115
-        }
116
-    }
117
-
118
-
119
-    /**
120
-     * Deletes a single model object and returns it. Unless
121
-     *
122
-     * @param WP_REST_Request $request
123
-     * @param string          $version
124
-     * @param string          $model_name
125
-     * @return WP_REST_Response
126
-     */
127
-    public static function handleRequestDelete(
128
-        WP_REST_Request $request,
129
-        string $version,
130
-        string $model_name
131
-    ): WP_REST_Response {
132
-        $controller = Write::getModelWriteController($version);
133
-        try {
134
-            return $controller->sendResponse(
135
-                $controller->delete(
136
-                    $controller->getModelVersionInfo()->loadModel($model_name),
137
-                    $request
138
-                )
139
-            );
140
-        } catch (Exception $e) {
141
-            return $controller->sendResponse($e);
142
-        }
143
-    }
144
-
145
-
146
-    /**
147
-     * Inserts a new model object according to the $request
148
-     *
149
-     * @param EEM_Base        $model
150
-     * @param WP_REST_Request $request
151
-     * @return array
152
-     * @throws EE_Error
153
-     * @throws RestException
154
-     * @throws ReflectionException
155
-     */
156
-    public function insert(EEM_Base $model, WP_REST_Request $request): array
157
-    {
158
-        Capabilities::verifyAtLeastPartialAccessTo($model, EEM_Base::caps_edit, 'create');
159
-        $default_cap_to_check_for = EE_Restriction_Generator_Base::get_default_restrictions_cap();
160
-        if (! current_user_can($default_cap_to_check_for)) {
161
-            throw new RestException(
162
-                'rest_cannot_create_' . EEH_Inflector::pluralize_and_lower(($model->get_this_model_name())),
163
-                sprintf(
164
-                    esc_html__(
165
-                    // @codingStandardsIgnoreStart
166
-                        'For now, only those with the admin capability to "%1$s" are allowed to use the REST API to insert data into Event Espresso.',
167
-                        // @codingStandardsIgnoreEnd
168
-                        'event_espresso'
169
-                    ),
170
-                    $default_cap_to_check_for
171
-                ),
172
-                ['status' => 403]
173
-            );
174
-        }
175
-        $submitted_json_data = array_merge((array) $request->get_body_params(), (array) $request->get_json_params());
176
-        $model_data          = ModelDataTranslator::prepareConditionsQueryParamsForModels(
177
-            $submitted_json_data,
178
-            $model,
179
-            $this->getModelVersionInfo()->requestedVersion(),
180
-            true
181
-        );
182
-        /** @var EE_Base_Class $model_obj */
183
-        $model_obj = EE_Registry::instance()->load_class(
184
-            $model->get_this_model_name(),
185
-            [$model_data, $model->get_timezone()],
186
-            false,
187
-            false
188
-        );
189
-        $model_obj->save();
190
-        $new_id = $model_obj->ID();
191
-        if (! $new_id) {
192
-            throw new RestException(
193
-                'rest_insertion_failed',
194
-                sprintf(
195
-                    esc_html__('Could not insert new %1$s', 'event_espresso'),
196
-                    $model->get_this_model_name()
197
-                )
198
-            );
199
-        }
200
-        return $this->returnModelObjAsJsonResponse($model_obj, $request);
201
-    }
202
-
203
-
204
-    /**
205
-     * Updates an existing model object according to the $request
206
-     *
207
-     * @param EEM_Base        $model
208
-     * @param WP_REST_Request $request
209
-     * @return array
210
-     * @throws EE_Error
211
-     * @throws ReflectionException
212
-     */
213
-    public function update(EEM_Base $model, WP_REST_Request $request): array
214
-    {
215
-        Capabilities::verifyAtLeastPartialAccessTo($model, EEM_Base::caps_edit, 'edit');
216
-        $default_cap_to_check_for = EE_Restriction_Generator_Base::get_default_restrictions_cap();
217
-        if (! current_user_can($default_cap_to_check_for)) {
218
-            throw new RestException(
219
-                'rest_cannot_edit_' . EEH_Inflector::pluralize_and_lower(($model->get_this_model_name())),
220
-                sprintf(
221
-                    esc_html__(
222
-                    // @codingStandardsIgnoreStart
223
-                        'For now, only those with the admin capability to "%1$s" are allowed to use the REST API to update data into Event Espresso.',
224
-                        // @codingStandardsIgnoreEnd
225
-                        'event_espresso'
226
-                    ),
227
-                    $default_cap_to_check_for
228
-                ),
229
-                ['status' => 403]
230
-            );
231
-        }
232
-        $obj_id = $request->get_param('id');
233
-        if (! $obj_id) {
234
-            throw new RestException(
235
-                'rest_edit_failed',
236
-                sprintf(esc_html__('Could not edit %1$s', 'event_espresso'), $model->get_this_model_name())
237
-            );
238
-        }
239
-        $model_data = ModelDataTranslator::prepareConditionsQueryParamsForModels(
240
-            $this->getBodyParams($request),
241
-            $model,
242
-            $this->getModelVersionInfo()->requestedVersion(),
243
-            true
244
-        );
245
-        $model_obj  = $model->get_one_by_ID($obj_id);
246
-        if (! $model_obj instanceof EE_Base_Class) {
247
-            $lowercase_model_name = strtolower($model->get_this_model_name());
248
-            throw new RestException(
249
-                sprintf('rest_%s_invalid_id', $lowercase_model_name),
250
-                sprintf(esc_html__('Invalid %s ID.', 'event_espresso'), $lowercase_model_name),
251
-                ['status' => 404]
252
-            );
253
-        }
254
-        $model_obj->save($model_data);
255
-        return $this->returnModelObjAsJsonResponse($model_obj, $request);
256
-    }
257
-
258
-
259
-    /**
260
-     * Updates an existing model object according to the $request
261
-     *
262
-     * @param EEM_Base        $model
263
-     * @param WP_REST_Request $request
264
-     * @return array of either the soft-deleted item, or
265
-     * @throws EE_Error
266
-     * @throws ReflectionException
267
-     */
268
-    public function delete(EEM_Base $model, WP_REST_Request $request): array
269
-    {
270
-        Capabilities::verifyAtLeastPartialAccessTo($model, EEM_Base::caps_delete, 'delete');
271
-        $default_cap_to_check_for = EE_Restriction_Generator_Base::get_default_restrictions_cap();
272
-        if (! current_user_can($default_cap_to_check_for)) {
273
-            throw new RestException(
274
-                'rest_cannot_delete_' . EEH_Inflector::pluralize_and_lower(($model->get_this_model_name())),
275
-                sprintf(
276
-                    esc_html__(
277
-                    // @codingStandardsIgnoreStart
278
-                        'For now, only those with the admin capability to "%1$s" are allowed to use the REST API to delete data into Event Espresso.',
279
-                        // @codingStandardsIgnoreEnd
280
-                        'event_espresso'
281
-                    ),
282
-                    $default_cap_to_check_for
283
-                ),
284
-                ['status' => 403]
285
-            );
286
-        }
287
-        $obj_id = $request->get_param('id');
288
-        // this is where we would apply more fine-grained caps
289
-        $model_obj = $model->get_one_by_ID($obj_id);
290
-        if (! $model_obj instanceof EE_Base_Class) {
291
-            $lowercase_model_name = strtolower($model->get_this_model_name());
292
-            throw new RestException(
293
-                sprintf('rest_%s_invalid_id', $lowercase_model_name),
294
-                sprintf(esc_html__('Invalid %s ID.', 'event_espresso'), $lowercase_model_name),
295
-                ['status' => 404]
296
-            );
297
-        }
298
-        $requested_permanent_delete = filter_var($request->get_param('force'), FILTER_VALIDATE_BOOLEAN);
299
-        $requested_allow_blocking   = filter_var($request->get_param('allow_blocking'), FILTER_VALIDATE_BOOLEAN);
300
-        if ($requested_permanent_delete) {
301
-            $previous = $this->returnModelObjAsJsonResponse($model_obj, $request);
302
-            $deleted  = (bool) $model->delete_permanently_by_ID($obj_id, $requested_allow_blocking);
303
-            return [
304
-                'deleted'  => $deleted,
305
-                'previous' => $previous,
306
-            ];
307
-        } else {
308
-            if ($model instanceof EEM_Soft_Delete_Base) {
309
-                $model->delete_by_ID($obj_id, $requested_allow_blocking);
310
-                return $this->returnModelObjAsJsonResponse($model_obj, $request);
311
-            } else {
312
-                throw new RestException(
313
-                    'rest_trash_not_supported',
314
-                    501,
315
-                    sprintf(
316
-                        esc_html__('%1$s do not support trashing. Set force=1 to delete.', 'event_espresso'),
317
-                        EEH_Inflector::pluralize($model->get_this_model_name())
318
-                    )
319
-                );
320
-            }
321
-        }
322
-    }
323
-
324
-
325
-    /**
326
-     * Returns an array ready to be converted into a JSON response, based solely on the model object
327
-     *
328
-     * @param EE_Base_Class   $model_obj
329
-     * @param WP_REST_Request $request
330
-     * @return array ready for a response
331
-     * @throws EE_Error
332
-     * @throws ReflectionException
333
-     */
334
-    protected function returnModelObjAsJsonResponse(EE_Base_Class $model_obj, WP_REST_Request $request): array
335
-    {
336
-        $model = $model_obj->get_model();
337
-        // create an array exactly like the wpdb results row,
338
-        // so we can pass it to controllers/model/Read::create_entity_from_wpdb_result()
339
-        $simulated_db_row = [];
340
-        foreach ($model->field_settings(true) as $field_name => $field_obj) {
341
-            // we need to reconstruct the normal wpdb results, including the db-only fields
342
-            // like a secondary table's primary key. The models expect those (but don't care what value they have)
343
-            if ($field_obj instanceof EE_DB_Only_Field_Base) {
344
-                $raw_value = true;
345
-            } elseif ($field_obj instanceof EE_Datetime_Field) {
346
-                $raw_value = $model_obj->get_DateTime_object($field_name);
347
-            } else {
348
-                $raw_value = $model_obj->get_raw($field_name);
349
-            }
350
-            $simulated_db_row[ $field_obj->get_qualified_column() ] = $field_obj->prepare_for_use_in_db($raw_value);
351
-        }
352
-        $read_controller = Read::getModelReadController($this->getRequestedVersion());
353
-        // the simulates request really doesn't need any info downstream
354
-        $simulated_request = new WP_REST_Request('GET');
355
-        // set the caps context on the simulated according to the original request.
356
-        switch ($request->get_method()) {
357
-            case 'POST':
358
-            case 'PUT':
359
-                $caps_context = EEM_Base::caps_edit;
360
-                break;
361
-            case 'DELETE':
362
-                $caps_context = EEM_Base::caps_delete;
363
-                break;
364
-            default:
365
-                $caps_context = EEM_Base::caps_read_admin;
366
-        }
367
-        $simulated_request->set_param('caps', $caps_context);
368
-        return $read_controller->createEntityFromWpdbResult(
369
-            $model_obj->get_model(),
370
-            $simulated_db_row,
371
-            $simulated_request
372
-        );
373
-    }
374
-
375
-
376
-    /**
377
-     * Gets the item affected by this request
378
-     *
379
-     * @param EEM_Base        $model
380
-     * @param WP_REST_Request $request
381
-     * @param int|string      $obj_id
382
-     * @return array
383
-     * @throws EE_Error
384
-     * @throws ReflectionException
385
-     */
386
-    protected function getOneBasedOnRequest(EEM_Base $model, WP_REST_Request $request, $obj_id): array
387
-    {
388
-        $requested_version = $this->getRequestedVersion($request->get_route());
389
-        $get_request       = new WP_REST_Request(
390
-            'GET',
391
-            EED_Core_Rest_Api::ee_api_namespace
392
-            . $requested_version
393
-            . '/'
394
-            . EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
395
-            . '/'
396
-            . $obj_id
397
-        );
398
-        $get_request->set_url_params(
399
-            [
400
-                'id'      => $obj_id,
401
-                'include' => $request->get_param('include'),
402
-            ]
403
-        );
404
-
405
-        $read_controller = Read::getModelReadController($this->getRequestedVersion());
406
-        return $read_controller->getEntityFromModel($model, $get_request);
407
-    }
408
-
409
-
410
-    /**
411
-     * Adds a relation between the specified models (if it doesn't already exist.)
412
-     *
413
-     * @param WP_REST_Request $request
414
-     * @param string          $version
415
-     * @param string          $model_name
416
-     * @param string          $related_model_name
417
-     * @return WP_REST_Response
418
-     * @since 4.9.76.p
419
-     */
420
-    public static function handleRequestAddRelation(
421
-        WP_REST_Request $request,
422
-        string $version,
423
-        string $model_name,
424
-        string $related_model_name
425
-    ): WP_REST_Response {
426
-        $controller = Write::getModelWriteController($version);
427
-        try {
428
-            $main_model = $controller->validateModel($model_name);
429
-            $controller->validateModel($related_model_name);
430
-            return $controller->sendResponse(
431
-                $controller->addRelation(
432
-                    $main_model,
433
-                    $main_model->related_settings_for($related_model_name),
434
-                    $request
435
-                )
436
-            );
437
-        } catch (Exception $e) {
438
-            return $controller->sendResponse($e);
439
-        }
440
-    }
441
-
442
-
443
-    /**
444
-     * Adds a relation between the two model specified model objects.
445
-     *
446
-     * @param EEM_Base               $model
447
-     * @param EE_Model_Relation_Base $relation
448
-     * @param WP_REST_Request        $request
449
-     * @return array
450
-     * @throws EE_Error
451
-     * @throws InvalidArgumentException
452
-     * @throws InvalidDataTypeException
453
-     * @throws InvalidInterfaceException
454
-     * @throws RestException
455
-     * @throws DomainException
456
-     * @throws ReflectionException
457
-     * @since 4.9.76.p
458
-     */
459
-    public function addRelation(EEM_Base $model, EE_Model_Relation_Base $relation, WP_REST_Request $request): array
460
-    {
461
-        [$model_obj, $other_obj] = $this->getBothModelObjects($model, $relation, $request);
462
-        $extra_params = [];
463
-        if ($relation instanceof EE_HABTM_Relation) {
464
-            $extra_params = array_intersect_key(
465
-                ModelDataTranslator::prepareConditionsQueryParamsForModels(
466
-                    $request->get_body_params(),
467
-                    $relation->get_join_model(),
468
-                    $this->getModelVersionInfo()->requestedVersion(),
469
-                    true
470
-                ),
471
-                $relation->getNonKeyFields()
472
-            );
473
-        }
474
-        // Add a relation.
475
-        $related_obj = $model_obj->_add_relation_to(
476
-            $other_obj,
477
-            $relation->get_other_model()->get_this_model_name(),
478
-            $extra_params
479
-        );
480
-        $response    = [
481
-            strtolower($model->get_this_model_name())                       => $this->returnModelObjAsJsonResponse(
482
-                $model_obj,
483
-                $request
484
-            ),
485
-            strtolower($relation->get_other_model()->get_this_model_name()) => $this->returnModelObjAsJsonResponse(
486
-                $related_obj,
487
-                $request
488
-            ),
489
-        ];
490
-        if ($relation instanceof EE_HABTM_Relation) {
491
-            $join_model_obj                                                                     =
492
-                $relation->get_join_model()->get_one(
493
-                    [
494
-                        [
495
-                            $relation->get_join_model()->get_foreign_key_to($model->get_this_model_name())->get_name(
496
-                            )             => $model_obj->ID(),
497
-                            $relation->get_join_model()->get_foreign_key_to(
498
-                                $relation->get_other_model()->get_this_model_name()
499
-                            )->get_name() => $related_obj->ID(),
500
-                        ],
501
-                    ]
502
-                );
503
-            $response['join'][ strtolower($relation->get_join_model()->get_this_model_name()) ] =
504
-                $this->returnModelObjAsJsonResponse($join_model_obj, $request);
505
-        }
506
-        return $response;
507
-    }
508
-
509
-
510
-    /**
511
-     * Removes the relation between the specified models (if it exists).
512
-     *
513
-     * @param WP_REST_Request $request
514
-     * @param                 $version
515
-     * @param                 $model_name
516
-     * @param                 $related_model_name
517
-     * @return WP_REST_Response
518
-     * @since 4.9.76.p
519
-     */
520
-    public static function handleRequestRemoveRelation(
521
-        WP_REST_Request $request,
522
-        $version,
523
-        $model_name,
524
-        $related_model_name
525
-    ): WP_REST_Response {
526
-        $controller = Write::getModelWriteController($version);
527
-        try {
528
-            $main_model = $controller->getModelVersionInfo()->loadModel($model_name);
529
-            return $controller->sendResponse(
530
-                $controller->removeRelation(
531
-                    $main_model,
532
-                    $main_model->related_settings_for($related_model_name),
533
-                    $request
534
-                )
535
-            );
536
-        } catch (Exception $e) {
537
-            return $controller->sendResponse($e);
538
-        }
539
-    }
540
-
541
-
542
-    /**
543
-     * Adds a relation between the two model specified model objects.
544
-     *
545
-     * @param EEM_Base               $model
546
-     * @param EE_Model_Relation_Base $relation
547
-     * @param WP_REST_Request        $request
548
-     * @return array
549
-     * @throws DomainException
550
-     * @throws EE_Error
551
-     * @throws InvalidArgumentException
552
-     * @throws InvalidDataTypeException
553
-     * @throws InvalidInterfaceException
554
-     * @throws RestException
555
-     * @throws ReflectionException
556
-     * @since 4.9.76.p
557
-     */
558
-    public function removeRelation(EEM_Base $model, EE_Model_Relation_Base $relation, WP_REST_Request $request): array
559
-    {
560
-        // This endpoint doesn't accept body parameters (it's understandable to think it might, so let developers know
561
-        // up-front that it doesn't.)
562
-        if (! empty($request->get_body_params())) {
563
-            $body_params = $request->get_body_params();
564
-            throw new RestException(
565
-                'invalid_field',
566
-                sprintf(
567
-                    esc_html__('This endpoint doesn\'t accept post body arguments, you sent in %1$s', 'event_espresso'),
568
-                    implode(array_keys($body_params))
569
-                )
570
-            );
571
-        }
572
-        [$model_obj, $other_obj] = $this->getBothModelObjects($model, $relation, $request);
573
-        // Remember the old relation, if it used a join entry.
574
-        $join_model_obj = null;
575
-        if ($relation instanceof EE_HABTM_Relation) {
576
-            $join_model_obj = $relation->get_join_model()->get_one(
577
-                [
578
-                    [
579
-                        $model->primary_key_name()                       => $model_obj->ID(),
580
-                        $relation->get_other_model()->primary_key_name() => $other_obj->ID(),
581
-                    ],
582
-                ]
583
-            );
584
-        }
585
-        // Remove the relation.
586
-        $related_obj = $model_obj->_remove_relation_to(
587
-            $other_obj,
588
-            $relation->get_other_model()->get_this_model_name()
589
-        );
590
-        $response    = [
591
-            strtolower($model->get_this_model_name())                       => $this->returnModelObjAsJsonResponse(
592
-                $model_obj,
593
-                $request
594
-            ),
595
-            strtolower($relation->get_other_model()->get_this_model_name()) => $this->returnModelObjAsJsonResponse(
596
-                $related_obj,
597
-                $request
598
-            ),
599
-        ];
600
-        if ($relation instanceof EE_HABTM_Relation) {
601
-            $join_model_obj_after_removal = $relation->get_join_model()->get_one(
602
-                [
603
-                    [
604
-                        $model->primary_key_name()                       => $model_obj->ID(),
605
-                        $relation->get_other_model()->primary_key_name() => $other_obj->ID(),
606
-                    ],
607
-                ]
608
-            );
609
-            if ($join_model_obj instanceof EE_Base_Class) {
610
-                $response['join'][ strtolower($relation->get_join_model()->get_this_model_name()) ] =
611
-                    $this->returnModelObjAsJsonResponse($join_model_obj, $request);
612
-            } else {
613
-                $response['join'][ strtolower($relation->get_join_model()->get_this_model_name()) ] = null;
614
-            }
615
-        }
616
-        return $response;
617
-    }
618
-
619
-
620
-    /**
621
-     * Gets the model objects indicated by the model, relation object, and request.
622
-     * Throws an exception if the first object doesn't exist, and currently if the related object also doesn't exist.
623
-     * However, this behaviour may change, as we may add support for simultaneously creating and relating data.
624
-     *
625
-     * @param EEM_Base               $model
626
-     * @param EE_Model_Relation_Base $relation
627
-     * @param WP_REST_Request        $request
628
-     * @return array {
629
-     * @type EE_Base_Class           $model_obj
630
-     * @type EE_Base_Class|null      $other_model_obj
631
-     * }
632
-     * @throws RestException
633
-     * @throws EE_Error
634
-     * @since 4.9.76.p
635
-     */
636
-    protected function getBothModelObjects(
637
-        EEM_Base $model,
638
-        EE_Model_Relation_Base $relation,
639
-        WP_REST_Request $request
640
-    ): array {
641
-        // Check generic caps. For now, we're only allowing access to this endpoint to full admins.
642
-        Capabilities::verifyAtLeastPartialAccessTo($model, EEM_Base::caps_edit, 'edit');
643
-        $default_cap_to_check_for = EE_Restriction_Generator_Base::get_default_restrictions_cap();
644
-        if (! current_user_can($default_cap_to_check_for)) {
645
-            throw new RestException(
646
-                'rest_cannot_edit_' . EEH_Inflector::pluralize_and_lower(($model->get_this_model_name())),
647
-                sprintf(
648
-                    esc_html__(
649
-                    // @codingStandardsIgnoreStart
650
-                        'For now, only those with the admin capability to "%1$s" are allowed to use the REST API to add relations in Event Espresso.',
651
-                        // @codingStandardsIgnoreEnd
652
-                        'event_espresso'
653
-                    ),
654
-                    $default_cap_to_check_for
655
-                ),
656
-                ['status' => 403]
657
-            );
658
-        }
659
-        // Get the main model object.
660
-        $model_obj = $this->getOneOrThrowException($model, $request->get_param('id'));
661
-        // For now, we require the other model object to exist too. This might be relaxed later.
662
-        $other_obj = $this->getOneOrThrowException($relation->get_other_model(), $request->get_param('related_id'));
663
-        return [$model_obj, $other_obj];
664
-    }
665
-
666
-
667
-    /**
668
-     * Gets the model with that ID or throws a REST exception.
669
-     *
670
-     * @param EEM_Base   $model
671
-     * @param int|string $id
672
-     * @return EE_Base_Class
673
-     * @throws EE_Error
674
-     * @throws RestException
675
-     * @since 4.9.76.p
676
-     */
677
-    protected function getOneOrThrowException(EEM_Base $model, $id): EE_Base_Class
678
-    {
679
-        $model_obj = $model->get_one_by_ID($id);
680
-        // @todo: check they can permission for it. For now unnecessary because only full admins can use this endpoint.
681
-        if ($model_obj instanceof EE_Base_Class) {
682
-            return $model_obj;
683
-        }
684
-        $lowercase_model_name = strtolower($model->get_this_model_name());
685
-        throw new RestException(
686
-            sprintf('rest_%s_invalid_id', $lowercase_model_name),
687
-            sprintf(esc_html__('Invalid %s ID.', 'event_espresso'), $lowercase_model_name),
688
-            ['status' => 404]
689
-        );
690
-    }
40
+	/**
41
+	 * @throws EE_Error
42
+	 * @throws ReflectionException
43
+	 */
44
+	public function __construct()
45
+	{
46
+		parent::__construct();
47
+		EE_Registry::instance()->load_helper('Inflector');
48
+	}
49
+
50
+
51
+	/**
52
+	 * @param string $version
53
+	 * @return Write
54
+	 * @since $VID:$
55
+	 */
56
+	public static function getModelWriteController(string $version): Write
57
+	{
58
+		/** @var Write $controller */
59
+		$controller = LoaderFactory::getLoader()->getNew(Write::class);
60
+		$controller->setRequestedVersion($version);
61
+		return $controller;
62
+	}
63
+
64
+
65
+	/**
66
+	 * Handles requests to get all (or a filtered subset) of entities for a particular model
67
+	 *
68
+	 * @param WP_REST_Request $request
69
+	 * @param string          $version
70
+	 * @param string          $model_name
71
+	 * @return WP_REST_Response
72
+	 */
73
+	public static function handleRequestInsert(
74
+		WP_REST_Request $request,
75
+		string $version,
76
+		string $model_name
77
+	): WP_REST_Response {
78
+		$controller = Write::getModelWriteController($version);
79
+		try {
80
+			return $controller->sendResponse(
81
+				$controller->insert(
82
+					$controller->getModelVersionInfo()->loadModel($model_name),
83
+					$request
84
+				)
85
+			);
86
+		} catch (Exception $e) {
87
+			return $controller->sendResponse($e);
88
+		}
89
+	}
90
+
91
+
92
+	/**
93
+	 * Handles a request from \WP_REST_Server to update an EE model
94
+	 *
95
+	 * @param WP_REST_Request $request
96
+	 * @param string          $version
97
+	 * @param string          $model_name
98
+	 * @return WP_REST_Response
99
+	 */
100
+	public static function handleRequestUpdate(
101
+		WP_REST_Request $request,
102
+		string $version,
103
+		string $model_name
104
+	): WP_REST_Response {
105
+		$controller = Write::getModelWriteController($version);
106
+		try {
107
+			return $controller->sendResponse(
108
+				$controller->update(
109
+					$controller->getModelVersionInfo()->loadModel($model_name),
110
+					$request
111
+				)
112
+			);
113
+		} catch (Exception $e) {
114
+			return $controller->sendResponse($e);
115
+		}
116
+	}
117
+
118
+
119
+	/**
120
+	 * Deletes a single model object and returns it. Unless
121
+	 *
122
+	 * @param WP_REST_Request $request
123
+	 * @param string          $version
124
+	 * @param string          $model_name
125
+	 * @return WP_REST_Response
126
+	 */
127
+	public static function handleRequestDelete(
128
+		WP_REST_Request $request,
129
+		string $version,
130
+		string $model_name
131
+	): WP_REST_Response {
132
+		$controller = Write::getModelWriteController($version);
133
+		try {
134
+			return $controller->sendResponse(
135
+				$controller->delete(
136
+					$controller->getModelVersionInfo()->loadModel($model_name),
137
+					$request
138
+				)
139
+			);
140
+		} catch (Exception $e) {
141
+			return $controller->sendResponse($e);
142
+		}
143
+	}
144
+
145
+
146
+	/**
147
+	 * Inserts a new model object according to the $request
148
+	 *
149
+	 * @param EEM_Base        $model
150
+	 * @param WP_REST_Request $request
151
+	 * @return array
152
+	 * @throws EE_Error
153
+	 * @throws RestException
154
+	 * @throws ReflectionException
155
+	 */
156
+	public function insert(EEM_Base $model, WP_REST_Request $request): array
157
+	{
158
+		Capabilities::verifyAtLeastPartialAccessTo($model, EEM_Base::caps_edit, 'create');
159
+		$default_cap_to_check_for = EE_Restriction_Generator_Base::get_default_restrictions_cap();
160
+		if (! current_user_can($default_cap_to_check_for)) {
161
+			throw new RestException(
162
+				'rest_cannot_create_' . EEH_Inflector::pluralize_and_lower(($model->get_this_model_name())),
163
+				sprintf(
164
+					esc_html__(
165
+					// @codingStandardsIgnoreStart
166
+						'For now, only those with the admin capability to "%1$s" are allowed to use the REST API to insert data into Event Espresso.',
167
+						// @codingStandardsIgnoreEnd
168
+						'event_espresso'
169
+					),
170
+					$default_cap_to_check_for
171
+				),
172
+				['status' => 403]
173
+			);
174
+		}
175
+		$submitted_json_data = array_merge((array) $request->get_body_params(), (array) $request->get_json_params());
176
+		$model_data          = ModelDataTranslator::prepareConditionsQueryParamsForModels(
177
+			$submitted_json_data,
178
+			$model,
179
+			$this->getModelVersionInfo()->requestedVersion(),
180
+			true
181
+		);
182
+		/** @var EE_Base_Class $model_obj */
183
+		$model_obj = EE_Registry::instance()->load_class(
184
+			$model->get_this_model_name(),
185
+			[$model_data, $model->get_timezone()],
186
+			false,
187
+			false
188
+		);
189
+		$model_obj->save();
190
+		$new_id = $model_obj->ID();
191
+		if (! $new_id) {
192
+			throw new RestException(
193
+				'rest_insertion_failed',
194
+				sprintf(
195
+					esc_html__('Could not insert new %1$s', 'event_espresso'),
196
+					$model->get_this_model_name()
197
+				)
198
+			);
199
+		}
200
+		return $this->returnModelObjAsJsonResponse($model_obj, $request);
201
+	}
202
+
203
+
204
+	/**
205
+	 * Updates an existing model object according to the $request
206
+	 *
207
+	 * @param EEM_Base        $model
208
+	 * @param WP_REST_Request $request
209
+	 * @return array
210
+	 * @throws EE_Error
211
+	 * @throws ReflectionException
212
+	 */
213
+	public function update(EEM_Base $model, WP_REST_Request $request): array
214
+	{
215
+		Capabilities::verifyAtLeastPartialAccessTo($model, EEM_Base::caps_edit, 'edit');
216
+		$default_cap_to_check_for = EE_Restriction_Generator_Base::get_default_restrictions_cap();
217
+		if (! current_user_can($default_cap_to_check_for)) {
218
+			throw new RestException(
219
+				'rest_cannot_edit_' . EEH_Inflector::pluralize_and_lower(($model->get_this_model_name())),
220
+				sprintf(
221
+					esc_html__(
222
+					// @codingStandardsIgnoreStart
223
+						'For now, only those with the admin capability to "%1$s" are allowed to use the REST API to update data into Event Espresso.',
224
+						// @codingStandardsIgnoreEnd
225
+						'event_espresso'
226
+					),
227
+					$default_cap_to_check_for
228
+				),
229
+				['status' => 403]
230
+			);
231
+		}
232
+		$obj_id = $request->get_param('id');
233
+		if (! $obj_id) {
234
+			throw new RestException(
235
+				'rest_edit_failed',
236
+				sprintf(esc_html__('Could not edit %1$s', 'event_espresso'), $model->get_this_model_name())
237
+			);
238
+		}
239
+		$model_data = ModelDataTranslator::prepareConditionsQueryParamsForModels(
240
+			$this->getBodyParams($request),
241
+			$model,
242
+			$this->getModelVersionInfo()->requestedVersion(),
243
+			true
244
+		);
245
+		$model_obj  = $model->get_one_by_ID($obj_id);
246
+		if (! $model_obj instanceof EE_Base_Class) {
247
+			$lowercase_model_name = strtolower($model->get_this_model_name());
248
+			throw new RestException(
249
+				sprintf('rest_%s_invalid_id', $lowercase_model_name),
250
+				sprintf(esc_html__('Invalid %s ID.', 'event_espresso'), $lowercase_model_name),
251
+				['status' => 404]
252
+			);
253
+		}
254
+		$model_obj->save($model_data);
255
+		return $this->returnModelObjAsJsonResponse($model_obj, $request);
256
+	}
257
+
258
+
259
+	/**
260
+	 * Updates an existing model object according to the $request
261
+	 *
262
+	 * @param EEM_Base        $model
263
+	 * @param WP_REST_Request $request
264
+	 * @return array of either the soft-deleted item, or
265
+	 * @throws EE_Error
266
+	 * @throws ReflectionException
267
+	 */
268
+	public function delete(EEM_Base $model, WP_REST_Request $request): array
269
+	{
270
+		Capabilities::verifyAtLeastPartialAccessTo($model, EEM_Base::caps_delete, 'delete');
271
+		$default_cap_to_check_for = EE_Restriction_Generator_Base::get_default_restrictions_cap();
272
+		if (! current_user_can($default_cap_to_check_for)) {
273
+			throw new RestException(
274
+				'rest_cannot_delete_' . EEH_Inflector::pluralize_and_lower(($model->get_this_model_name())),
275
+				sprintf(
276
+					esc_html__(
277
+					// @codingStandardsIgnoreStart
278
+						'For now, only those with the admin capability to "%1$s" are allowed to use the REST API to delete data into Event Espresso.',
279
+						// @codingStandardsIgnoreEnd
280
+						'event_espresso'
281
+					),
282
+					$default_cap_to_check_for
283
+				),
284
+				['status' => 403]
285
+			);
286
+		}
287
+		$obj_id = $request->get_param('id');
288
+		// this is where we would apply more fine-grained caps
289
+		$model_obj = $model->get_one_by_ID($obj_id);
290
+		if (! $model_obj instanceof EE_Base_Class) {
291
+			$lowercase_model_name = strtolower($model->get_this_model_name());
292
+			throw new RestException(
293
+				sprintf('rest_%s_invalid_id', $lowercase_model_name),
294
+				sprintf(esc_html__('Invalid %s ID.', 'event_espresso'), $lowercase_model_name),
295
+				['status' => 404]
296
+			);
297
+		}
298
+		$requested_permanent_delete = filter_var($request->get_param('force'), FILTER_VALIDATE_BOOLEAN);
299
+		$requested_allow_blocking   = filter_var($request->get_param('allow_blocking'), FILTER_VALIDATE_BOOLEAN);
300
+		if ($requested_permanent_delete) {
301
+			$previous = $this->returnModelObjAsJsonResponse($model_obj, $request);
302
+			$deleted  = (bool) $model->delete_permanently_by_ID($obj_id, $requested_allow_blocking);
303
+			return [
304
+				'deleted'  => $deleted,
305
+				'previous' => $previous,
306
+			];
307
+		} else {
308
+			if ($model instanceof EEM_Soft_Delete_Base) {
309
+				$model->delete_by_ID($obj_id, $requested_allow_blocking);
310
+				return $this->returnModelObjAsJsonResponse($model_obj, $request);
311
+			} else {
312
+				throw new RestException(
313
+					'rest_trash_not_supported',
314
+					501,
315
+					sprintf(
316
+						esc_html__('%1$s do not support trashing. Set force=1 to delete.', 'event_espresso'),
317
+						EEH_Inflector::pluralize($model->get_this_model_name())
318
+					)
319
+				);
320
+			}
321
+		}
322
+	}
323
+
324
+
325
+	/**
326
+	 * Returns an array ready to be converted into a JSON response, based solely on the model object
327
+	 *
328
+	 * @param EE_Base_Class   $model_obj
329
+	 * @param WP_REST_Request $request
330
+	 * @return array ready for a response
331
+	 * @throws EE_Error
332
+	 * @throws ReflectionException
333
+	 */
334
+	protected function returnModelObjAsJsonResponse(EE_Base_Class $model_obj, WP_REST_Request $request): array
335
+	{
336
+		$model = $model_obj->get_model();
337
+		// create an array exactly like the wpdb results row,
338
+		// so we can pass it to controllers/model/Read::create_entity_from_wpdb_result()
339
+		$simulated_db_row = [];
340
+		foreach ($model->field_settings(true) as $field_name => $field_obj) {
341
+			// we need to reconstruct the normal wpdb results, including the db-only fields
342
+			// like a secondary table's primary key. The models expect those (but don't care what value they have)
343
+			if ($field_obj instanceof EE_DB_Only_Field_Base) {
344
+				$raw_value = true;
345
+			} elseif ($field_obj instanceof EE_Datetime_Field) {
346
+				$raw_value = $model_obj->get_DateTime_object($field_name);
347
+			} else {
348
+				$raw_value = $model_obj->get_raw($field_name);
349
+			}
350
+			$simulated_db_row[ $field_obj->get_qualified_column() ] = $field_obj->prepare_for_use_in_db($raw_value);
351
+		}
352
+		$read_controller = Read::getModelReadController($this->getRequestedVersion());
353
+		// the simulates request really doesn't need any info downstream
354
+		$simulated_request = new WP_REST_Request('GET');
355
+		// set the caps context on the simulated according to the original request.
356
+		switch ($request->get_method()) {
357
+			case 'POST':
358
+			case 'PUT':
359
+				$caps_context = EEM_Base::caps_edit;
360
+				break;
361
+			case 'DELETE':
362
+				$caps_context = EEM_Base::caps_delete;
363
+				break;
364
+			default:
365
+				$caps_context = EEM_Base::caps_read_admin;
366
+		}
367
+		$simulated_request->set_param('caps', $caps_context);
368
+		return $read_controller->createEntityFromWpdbResult(
369
+			$model_obj->get_model(),
370
+			$simulated_db_row,
371
+			$simulated_request
372
+		);
373
+	}
374
+
375
+
376
+	/**
377
+	 * Gets the item affected by this request
378
+	 *
379
+	 * @param EEM_Base        $model
380
+	 * @param WP_REST_Request $request
381
+	 * @param int|string      $obj_id
382
+	 * @return array
383
+	 * @throws EE_Error
384
+	 * @throws ReflectionException
385
+	 */
386
+	protected function getOneBasedOnRequest(EEM_Base $model, WP_REST_Request $request, $obj_id): array
387
+	{
388
+		$requested_version = $this->getRequestedVersion($request->get_route());
389
+		$get_request       = new WP_REST_Request(
390
+			'GET',
391
+			EED_Core_Rest_Api::ee_api_namespace
392
+			. $requested_version
393
+			. '/'
394
+			. EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
395
+			. '/'
396
+			. $obj_id
397
+		);
398
+		$get_request->set_url_params(
399
+			[
400
+				'id'      => $obj_id,
401
+				'include' => $request->get_param('include'),
402
+			]
403
+		);
404
+
405
+		$read_controller = Read::getModelReadController($this->getRequestedVersion());
406
+		return $read_controller->getEntityFromModel($model, $get_request);
407
+	}
408
+
409
+
410
+	/**
411
+	 * Adds a relation between the specified models (if it doesn't already exist.)
412
+	 *
413
+	 * @param WP_REST_Request $request
414
+	 * @param string          $version
415
+	 * @param string          $model_name
416
+	 * @param string          $related_model_name
417
+	 * @return WP_REST_Response
418
+	 * @since 4.9.76.p
419
+	 */
420
+	public static function handleRequestAddRelation(
421
+		WP_REST_Request $request,
422
+		string $version,
423
+		string $model_name,
424
+		string $related_model_name
425
+	): WP_REST_Response {
426
+		$controller = Write::getModelWriteController($version);
427
+		try {
428
+			$main_model = $controller->validateModel($model_name);
429
+			$controller->validateModel($related_model_name);
430
+			return $controller->sendResponse(
431
+				$controller->addRelation(
432
+					$main_model,
433
+					$main_model->related_settings_for($related_model_name),
434
+					$request
435
+				)
436
+			);
437
+		} catch (Exception $e) {
438
+			return $controller->sendResponse($e);
439
+		}
440
+	}
441
+
442
+
443
+	/**
444
+	 * Adds a relation between the two model specified model objects.
445
+	 *
446
+	 * @param EEM_Base               $model
447
+	 * @param EE_Model_Relation_Base $relation
448
+	 * @param WP_REST_Request        $request
449
+	 * @return array
450
+	 * @throws EE_Error
451
+	 * @throws InvalidArgumentException
452
+	 * @throws InvalidDataTypeException
453
+	 * @throws InvalidInterfaceException
454
+	 * @throws RestException
455
+	 * @throws DomainException
456
+	 * @throws ReflectionException
457
+	 * @since 4.9.76.p
458
+	 */
459
+	public function addRelation(EEM_Base $model, EE_Model_Relation_Base $relation, WP_REST_Request $request): array
460
+	{
461
+		[$model_obj, $other_obj] = $this->getBothModelObjects($model, $relation, $request);
462
+		$extra_params = [];
463
+		if ($relation instanceof EE_HABTM_Relation) {
464
+			$extra_params = array_intersect_key(
465
+				ModelDataTranslator::prepareConditionsQueryParamsForModels(
466
+					$request->get_body_params(),
467
+					$relation->get_join_model(),
468
+					$this->getModelVersionInfo()->requestedVersion(),
469
+					true
470
+				),
471
+				$relation->getNonKeyFields()
472
+			);
473
+		}
474
+		// Add a relation.
475
+		$related_obj = $model_obj->_add_relation_to(
476
+			$other_obj,
477
+			$relation->get_other_model()->get_this_model_name(),
478
+			$extra_params
479
+		);
480
+		$response    = [
481
+			strtolower($model->get_this_model_name())                       => $this->returnModelObjAsJsonResponse(
482
+				$model_obj,
483
+				$request
484
+			),
485
+			strtolower($relation->get_other_model()->get_this_model_name()) => $this->returnModelObjAsJsonResponse(
486
+				$related_obj,
487
+				$request
488
+			),
489
+		];
490
+		if ($relation instanceof EE_HABTM_Relation) {
491
+			$join_model_obj                                                                     =
492
+				$relation->get_join_model()->get_one(
493
+					[
494
+						[
495
+							$relation->get_join_model()->get_foreign_key_to($model->get_this_model_name())->get_name(
496
+							)             => $model_obj->ID(),
497
+							$relation->get_join_model()->get_foreign_key_to(
498
+								$relation->get_other_model()->get_this_model_name()
499
+							)->get_name() => $related_obj->ID(),
500
+						],
501
+					]
502
+				);
503
+			$response['join'][ strtolower($relation->get_join_model()->get_this_model_name()) ] =
504
+				$this->returnModelObjAsJsonResponse($join_model_obj, $request);
505
+		}
506
+		return $response;
507
+	}
508
+
509
+
510
+	/**
511
+	 * Removes the relation between the specified models (if it exists).
512
+	 *
513
+	 * @param WP_REST_Request $request
514
+	 * @param                 $version
515
+	 * @param                 $model_name
516
+	 * @param                 $related_model_name
517
+	 * @return WP_REST_Response
518
+	 * @since 4.9.76.p
519
+	 */
520
+	public static function handleRequestRemoveRelation(
521
+		WP_REST_Request $request,
522
+		$version,
523
+		$model_name,
524
+		$related_model_name
525
+	): WP_REST_Response {
526
+		$controller = Write::getModelWriteController($version);
527
+		try {
528
+			$main_model = $controller->getModelVersionInfo()->loadModel($model_name);
529
+			return $controller->sendResponse(
530
+				$controller->removeRelation(
531
+					$main_model,
532
+					$main_model->related_settings_for($related_model_name),
533
+					$request
534
+				)
535
+			);
536
+		} catch (Exception $e) {
537
+			return $controller->sendResponse($e);
538
+		}
539
+	}
540
+
541
+
542
+	/**
543
+	 * Adds a relation between the two model specified model objects.
544
+	 *
545
+	 * @param EEM_Base               $model
546
+	 * @param EE_Model_Relation_Base $relation
547
+	 * @param WP_REST_Request        $request
548
+	 * @return array
549
+	 * @throws DomainException
550
+	 * @throws EE_Error
551
+	 * @throws InvalidArgumentException
552
+	 * @throws InvalidDataTypeException
553
+	 * @throws InvalidInterfaceException
554
+	 * @throws RestException
555
+	 * @throws ReflectionException
556
+	 * @since 4.9.76.p
557
+	 */
558
+	public function removeRelation(EEM_Base $model, EE_Model_Relation_Base $relation, WP_REST_Request $request): array
559
+	{
560
+		// This endpoint doesn't accept body parameters (it's understandable to think it might, so let developers know
561
+		// up-front that it doesn't.)
562
+		if (! empty($request->get_body_params())) {
563
+			$body_params = $request->get_body_params();
564
+			throw new RestException(
565
+				'invalid_field',
566
+				sprintf(
567
+					esc_html__('This endpoint doesn\'t accept post body arguments, you sent in %1$s', 'event_espresso'),
568
+					implode(array_keys($body_params))
569
+				)
570
+			);
571
+		}
572
+		[$model_obj, $other_obj] = $this->getBothModelObjects($model, $relation, $request);
573
+		// Remember the old relation, if it used a join entry.
574
+		$join_model_obj = null;
575
+		if ($relation instanceof EE_HABTM_Relation) {
576
+			$join_model_obj = $relation->get_join_model()->get_one(
577
+				[
578
+					[
579
+						$model->primary_key_name()                       => $model_obj->ID(),
580
+						$relation->get_other_model()->primary_key_name() => $other_obj->ID(),
581
+					],
582
+				]
583
+			);
584
+		}
585
+		// Remove the relation.
586
+		$related_obj = $model_obj->_remove_relation_to(
587
+			$other_obj,
588
+			$relation->get_other_model()->get_this_model_name()
589
+		);
590
+		$response    = [
591
+			strtolower($model->get_this_model_name())                       => $this->returnModelObjAsJsonResponse(
592
+				$model_obj,
593
+				$request
594
+			),
595
+			strtolower($relation->get_other_model()->get_this_model_name()) => $this->returnModelObjAsJsonResponse(
596
+				$related_obj,
597
+				$request
598
+			),
599
+		];
600
+		if ($relation instanceof EE_HABTM_Relation) {
601
+			$join_model_obj_after_removal = $relation->get_join_model()->get_one(
602
+				[
603
+					[
604
+						$model->primary_key_name()                       => $model_obj->ID(),
605
+						$relation->get_other_model()->primary_key_name() => $other_obj->ID(),
606
+					],
607
+				]
608
+			);
609
+			if ($join_model_obj instanceof EE_Base_Class) {
610
+				$response['join'][ strtolower($relation->get_join_model()->get_this_model_name()) ] =
611
+					$this->returnModelObjAsJsonResponse($join_model_obj, $request);
612
+			} else {
613
+				$response['join'][ strtolower($relation->get_join_model()->get_this_model_name()) ] = null;
614
+			}
615
+		}
616
+		return $response;
617
+	}
618
+
619
+
620
+	/**
621
+	 * Gets the model objects indicated by the model, relation object, and request.
622
+	 * Throws an exception if the first object doesn't exist, and currently if the related object also doesn't exist.
623
+	 * However, this behaviour may change, as we may add support for simultaneously creating and relating data.
624
+	 *
625
+	 * @param EEM_Base               $model
626
+	 * @param EE_Model_Relation_Base $relation
627
+	 * @param WP_REST_Request        $request
628
+	 * @return array {
629
+	 * @type EE_Base_Class           $model_obj
630
+	 * @type EE_Base_Class|null      $other_model_obj
631
+	 * }
632
+	 * @throws RestException
633
+	 * @throws EE_Error
634
+	 * @since 4.9.76.p
635
+	 */
636
+	protected function getBothModelObjects(
637
+		EEM_Base $model,
638
+		EE_Model_Relation_Base $relation,
639
+		WP_REST_Request $request
640
+	): array {
641
+		// Check generic caps. For now, we're only allowing access to this endpoint to full admins.
642
+		Capabilities::verifyAtLeastPartialAccessTo($model, EEM_Base::caps_edit, 'edit');
643
+		$default_cap_to_check_for = EE_Restriction_Generator_Base::get_default_restrictions_cap();
644
+		if (! current_user_can($default_cap_to_check_for)) {
645
+			throw new RestException(
646
+				'rest_cannot_edit_' . EEH_Inflector::pluralize_and_lower(($model->get_this_model_name())),
647
+				sprintf(
648
+					esc_html__(
649
+					// @codingStandardsIgnoreStart
650
+						'For now, only those with the admin capability to "%1$s" are allowed to use the REST API to add relations in Event Espresso.',
651
+						// @codingStandardsIgnoreEnd
652
+						'event_espresso'
653
+					),
654
+					$default_cap_to_check_for
655
+				),
656
+				['status' => 403]
657
+			);
658
+		}
659
+		// Get the main model object.
660
+		$model_obj = $this->getOneOrThrowException($model, $request->get_param('id'));
661
+		// For now, we require the other model object to exist too. This might be relaxed later.
662
+		$other_obj = $this->getOneOrThrowException($relation->get_other_model(), $request->get_param('related_id'));
663
+		return [$model_obj, $other_obj];
664
+	}
665
+
666
+
667
+	/**
668
+	 * Gets the model with that ID or throws a REST exception.
669
+	 *
670
+	 * @param EEM_Base   $model
671
+	 * @param int|string $id
672
+	 * @return EE_Base_Class
673
+	 * @throws EE_Error
674
+	 * @throws RestException
675
+	 * @since 4.9.76.p
676
+	 */
677
+	protected function getOneOrThrowException(EEM_Base $model, $id): EE_Base_Class
678
+	{
679
+		$model_obj = $model->get_one_by_ID($id);
680
+		// @todo: check they can permission for it. For now unnecessary because only full admins can use this endpoint.
681
+		if ($model_obj instanceof EE_Base_Class) {
682
+			return $model_obj;
683
+		}
684
+		$lowercase_model_name = strtolower($model->get_this_model_name());
685
+		throw new RestException(
686
+			sprintf('rest_%s_invalid_id', $lowercase_model_name),
687
+			sprintf(esc_html__('Invalid %s ID.', 'event_espresso'), $lowercase_model_name),
688
+			['status' => 404]
689
+		);
690
+	}
691 691
 }
Please login to merge, or discard this patch.
Spacing   +21 added lines, -21 removed lines patch added patch discarded remove patch
@@ -157,9 +157,9 @@  discard block
 block discarded – undo
157 157
     {
158 158
         Capabilities::verifyAtLeastPartialAccessTo($model, EEM_Base::caps_edit, 'create');
159 159
         $default_cap_to_check_for = EE_Restriction_Generator_Base::get_default_restrictions_cap();
160
-        if (! current_user_can($default_cap_to_check_for)) {
160
+        if ( ! current_user_can($default_cap_to_check_for)) {
161 161
             throw new RestException(
162
-                'rest_cannot_create_' . EEH_Inflector::pluralize_and_lower(($model->get_this_model_name())),
162
+                'rest_cannot_create_'.EEH_Inflector::pluralize_and_lower(($model->get_this_model_name())),
163 163
                 sprintf(
164 164
                     esc_html__(
165 165
                     // @codingStandardsIgnoreStart
@@ -188,7 +188,7 @@  discard block
 block discarded – undo
188 188
         );
189 189
         $model_obj->save();
190 190
         $new_id = $model_obj->ID();
191
-        if (! $new_id) {
191
+        if ( ! $new_id) {
192 192
             throw new RestException(
193 193
                 'rest_insertion_failed',
194 194
                 sprintf(
@@ -214,9 +214,9 @@  discard block
 block discarded – undo
214 214
     {
215 215
         Capabilities::verifyAtLeastPartialAccessTo($model, EEM_Base::caps_edit, 'edit');
216 216
         $default_cap_to_check_for = EE_Restriction_Generator_Base::get_default_restrictions_cap();
217
-        if (! current_user_can($default_cap_to_check_for)) {
217
+        if ( ! current_user_can($default_cap_to_check_for)) {
218 218
             throw new RestException(
219
-                'rest_cannot_edit_' . EEH_Inflector::pluralize_and_lower(($model->get_this_model_name())),
219
+                'rest_cannot_edit_'.EEH_Inflector::pluralize_and_lower(($model->get_this_model_name())),
220 220
                 sprintf(
221 221
                     esc_html__(
222 222
                     // @codingStandardsIgnoreStart
@@ -230,7 +230,7 @@  discard block
 block discarded – undo
230 230
             );
231 231
         }
232 232
         $obj_id = $request->get_param('id');
233
-        if (! $obj_id) {
233
+        if ( ! $obj_id) {
234 234
             throw new RestException(
235 235
                 'rest_edit_failed',
236 236
                 sprintf(esc_html__('Could not edit %1$s', 'event_espresso'), $model->get_this_model_name())
@@ -242,8 +242,8 @@  discard block
 block discarded – undo
242 242
             $this->getModelVersionInfo()->requestedVersion(),
243 243
             true
244 244
         );
245
-        $model_obj  = $model->get_one_by_ID($obj_id);
246
-        if (! $model_obj instanceof EE_Base_Class) {
245
+        $model_obj = $model->get_one_by_ID($obj_id);
246
+        if ( ! $model_obj instanceof EE_Base_Class) {
247 247
             $lowercase_model_name = strtolower($model->get_this_model_name());
248 248
             throw new RestException(
249 249
                 sprintf('rest_%s_invalid_id', $lowercase_model_name),
@@ -269,9 +269,9 @@  discard block
 block discarded – undo
269 269
     {
270 270
         Capabilities::verifyAtLeastPartialAccessTo($model, EEM_Base::caps_delete, 'delete');
271 271
         $default_cap_to_check_for = EE_Restriction_Generator_Base::get_default_restrictions_cap();
272
-        if (! current_user_can($default_cap_to_check_for)) {
272
+        if ( ! current_user_can($default_cap_to_check_for)) {
273 273
             throw new RestException(
274
-                'rest_cannot_delete_' . EEH_Inflector::pluralize_and_lower(($model->get_this_model_name())),
274
+                'rest_cannot_delete_'.EEH_Inflector::pluralize_and_lower(($model->get_this_model_name())),
275 275
                 sprintf(
276 276
                     esc_html__(
277 277
                     // @codingStandardsIgnoreStart
@@ -287,7 +287,7 @@  discard block
 block discarded – undo
287 287
         $obj_id = $request->get_param('id');
288 288
         // this is where we would apply more fine-grained caps
289 289
         $model_obj = $model->get_one_by_ID($obj_id);
290
-        if (! $model_obj instanceof EE_Base_Class) {
290
+        if ( ! $model_obj instanceof EE_Base_Class) {
291 291
             $lowercase_model_name = strtolower($model->get_this_model_name());
292 292
             throw new RestException(
293 293
                 sprintf('rest_%s_invalid_id', $lowercase_model_name),
@@ -347,7 +347,7 @@  discard block
 block discarded – undo
347 347
             } else {
348 348
                 $raw_value = $model_obj->get_raw($field_name);
349 349
             }
350
-            $simulated_db_row[ $field_obj->get_qualified_column() ] = $field_obj->prepare_for_use_in_db($raw_value);
350
+            $simulated_db_row[$field_obj->get_qualified_column()] = $field_obj->prepare_for_use_in_db($raw_value);
351 351
         }
352 352
         $read_controller = Read::getModelReadController($this->getRequestedVersion());
353 353
         // the simulates request really doesn't need any info downstream
@@ -477,7 +477,7 @@  discard block
 block discarded – undo
477 477
             $relation->get_other_model()->get_this_model_name(),
478 478
             $extra_params
479 479
         );
480
-        $response    = [
480
+        $response = [
481 481
             strtolower($model->get_this_model_name())                       => $this->returnModelObjAsJsonResponse(
482 482
                 $model_obj,
483 483
                 $request
@@ -488,7 +488,7 @@  discard block
 block discarded – undo
488 488
             ),
489 489
         ];
490 490
         if ($relation instanceof EE_HABTM_Relation) {
491
-            $join_model_obj                                                                     =
491
+            $join_model_obj =
492 492
                 $relation->get_join_model()->get_one(
493 493
                     [
494 494
                         [
@@ -500,7 +500,7 @@  discard block
 block discarded – undo
500 500
                         ],
501 501
                     ]
502 502
                 );
503
-            $response['join'][ strtolower($relation->get_join_model()->get_this_model_name()) ] =
503
+            $response['join'][strtolower($relation->get_join_model()->get_this_model_name())] =
504 504
                 $this->returnModelObjAsJsonResponse($join_model_obj, $request);
505 505
         }
506 506
         return $response;
@@ -559,7 +559,7 @@  discard block
 block discarded – undo
559 559
     {
560 560
         // This endpoint doesn't accept body parameters (it's understandable to think it might, so let developers know
561 561
         // up-front that it doesn't.)
562
-        if (! empty($request->get_body_params())) {
562
+        if ( ! empty($request->get_body_params())) {
563 563
             $body_params = $request->get_body_params();
564 564
             throw new RestException(
565 565
                 'invalid_field',
@@ -587,7 +587,7 @@  discard block
 block discarded – undo
587 587
             $other_obj,
588 588
             $relation->get_other_model()->get_this_model_name()
589 589
         );
590
-        $response    = [
590
+        $response = [
591 591
             strtolower($model->get_this_model_name())                       => $this->returnModelObjAsJsonResponse(
592 592
                 $model_obj,
593 593
                 $request
@@ -607,10 +607,10 @@  discard block
 block discarded – undo
607 607
                 ]
608 608
             );
609 609
             if ($join_model_obj instanceof EE_Base_Class) {
610
-                $response['join'][ strtolower($relation->get_join_model()->get_this_model_name()) ] =
610
+                $response['join'][strtolower($relation->get_join_model()->get_this_model_name())] =
611 611
                     $this->returnModelObjAsJsonResponse($join_model_obj, $request);
612 612
             } else {
613
-                $response['join'][ strtolower($relation->get_join_model()->get_this_model_name()) ] = null;
613
+                $response['join'][strtolower($relation->get_join_model()->get_this_model_name())] = null;
614 614
             }
615 615
         }
616 616
         return $response;
@@ -641,9 +641,9 @@  discard block
 block discarded – undo
641 641
         // Check generic caps. For now, we're only allowing access to this endpoint to full admins.
642 642
         Capabilities::verifyAtLeastPartialAccessTo($model, EEM_Base::caps_edit, 'edit');
643 643
         $default_cap_to_check_for = EE_Restriction_Generator_Base::get_default_restrictions_cap();
644
-        if (! current_user_can($default_cap_to_check_for)) {
644
+        if ( ! current_user_can($default_cap_to_check_for)) {
645 645
             throw new RestException(
646
-                'rest_cannot_edit_' . EEH_Inflector::pluralize_and_lower(($model->get_this_model_name())),
646
+                'rest_cannot_edit_'.EEH_Inflector::pluralize_and_lower(($model->get_this_model_name())),
647 647
                 sprintf(
648 648
                     esc_html__(
649 649
                     // @codingStandardsIgnoreStart
Please login to merge, or discard this patch.
core/libraries/rest_api/controllers/model/Meta.php 2 patches
Indentation   +114 added lines, -114 removed lines patch added patch discarded remove patch
@@ -25,124 +25,124 @@
 block discarded – undo
25 25
  */
26 26
 class Meta extends Base
27 27
 {
28
-    /**
29
-     * @param WP_REST_Request $request
30
-     * @param string          $version
31
-     * @return WP_REST_Response
32
-     */
33
-    public static function handleRequestModelsMeta(WP_REST_Request $request, string $version): WP_REST_Response
34
-    {
35
-        $controller = new Meta();
36
-        try {
37
-            $controller->setRequestedVersion($version);
38
-            return $controller->sendResponse($controller->getModelsMetadataEntity());
39
-        } catch (Exception $e) {
40
-            return $controller->sendResponse($e);
41
-        }
42
-    }
28
+	/**
29
+	 * @param WP_REST_Request $request
30
+	 * @param string          $version
31
+	 * @return WP_REST_Response
32
+	 */
33
+	public static function handleRequestModelsMeta(WP_REST_Request $request, string $version): WP_REST_Response
34
+	{
35
+		$controller = new Meta();
36
+		try {
37
+			$controller->setRequestedVersion($version);
38
+			return $controller->sendResponse($controller->getModelsMetadataEntity());
39
+		} catch (Exception $e) {
40
+			return $controller->sendResponse($e);
41
+		}
42
+	}
43 43
 
44 44
 
45
-    /**
46
-     * Gets the model metadata resource entity
47
-     *
48
-     * @return array for JSON response, describing all the models available in teh requested version
49
-     * @throws EE_Error
50
-     */
51
-    protected function getModelsMetadataEntity(): array
52
-    {
53
-        $response = [];
54
-        foreach ($this->getModelVersionInfo()->modelsForRequestedVersion() as $model_name => $model_classname) {
55
-            $model       = $this->getModelVersionInfo()->loadModel($model_name);
56
-            $fields_json = [];
57
-            foreach ($this->getModelVersionInfo()->fieldsOnModelInThisVersion($model) as $field_name => $field_obj) {
58
-                if ($this->getModelVersionInfo()->fieldIsIgnored($field_obj)) {
59
-                    continue;
60
-                }
61
-                if ($field_obj instanceof EE_Boolean_Field) {
62
-                    $datatype = 'Boolean';
63
-                } elseif ($field_obj->get_wpdb_data_type() == '%d') {
64
-                    $datatype = 'Number';
65
-                } elseif ($field_name instanceof EE_Serialized_Text_Field) {
66
-                    $datatype = 'Object';
67
-                } else {
68
-                    $datatype = 'String';
69
-                }
70
-                $default_value                      = ModelDataTranslator::prepareFieldValueForJson(
71
-                    $field_obj,
72
-                    $field_obj->get_default_value(),
73
-                    $this->getModelVersionInfo()->requestedVersion()
74
-                );
75
-                $field_json                         = [
76
-                    'name'                => $field_name,
77
-                    'nicename'            => wp_specialchars_decode($field_obj->get_nicename(), ENT_QUOTES),
78
-                    'has_rendered_format' => $this->getModelVersionInfo()->fieldHasRenderedFormat($field_obj),
79
-                    'has_pretty_format'   => $this->getModelVersionInfo()->fieldHasPrettyFormat($field_obj),
80
-                    'type'                => str_replace('EE_', '', get_class($field_obj)),
81
-                    'datatype'            => $datatype,
82
-                    'nullable'            => $field_obj->is_nullable(),
83
-                    'default'             => $default_value,
84
-                    'table_alias'         => $field_obj->get_table_alias(),
85
-                    'table_column'        => $field_obj->get_table_column(),
86
-                ];
87
-                $fields_json[ $field_json['name'] ] = $field_json;
88
-            }
89
-            $fields_json                       = array_merge(
90
-                $fields_json,
91
-                $this->getModelVersionInfo()->extraResourcePropertiesForModel($model)
92
-            );
93
-            $response[ $model_name ]['fields'] = apply_filters(
94
-                'FHEE__Meta__handle_request_models_meta__fields',
95
-                $fields_json,
96
-                $model
97
-            );
98
-            $relations_json                    = [];
99
-            foreach ($model->relation_settings() as $relation_name => $relation_obj) {
100
-                $relation_json                    = [
101
-                    'name'   => $relation_name,
102
-                    'type'   => str_replace('EE_', '', get_class($relation_obj)),
103
-                    'single' => $relation_obj instanceof EE_Belongs_To_Relation,
104
-                ];
105
-                $relations_json[ $relation_name ] = $relation_json;
106
-            }
107
-            $response[ $model_name ]['relations'] = apply_filters(
108
-                'FHEE__Meta__handle_request_models_meta__relations',
109
-                $relations_json,
110
-                $model
111
-            );
112
-        }
113
-        return $response;
114
-    }
45
+	/**
46
+	 * Gets the model metadata resource entity
47
+	 *
48
+	 * @return array for JSON response, describing all the models available in teh requested version
49
+	 * @throws EE_Error
50
+	 */
51
+	protected function getModelsMetadataEntity(): array
52
+	{
53
+		$response = [];
54
+		foreach ($this->getModelVersionInfo()->modelsForRequestedVersion() as $model_name => $model_classname) {
55
+			$model       = $this->getModelVersionInfo()->loadModel($model_name);
56
+			$fields_json = [];
57
+			foreach ($this->getModelVersionInfo()->fieldsOnModelInThisVersion($model) as $field_name => $field_obj) {
58
+				if ($this->getModelVersionInfo()->fieldIsIgnored($field_obj)) {
59
+					continue;
60
+				}
61
+				if ($field_obj instanceof EE_Boolean_Field) {
62
+					$datatype = 'Boolean';
63
+				} elseif ($field_obj->get_wpdb_data_type() == '%d') {
64
+					$datatype = 'Number';
65
+				} elseif ($field_name instanceof EE_Serialized_Text_Field) {
66
+					$datatype = 'Object';
67
+				} else {
68
+					$datatype = 'String';
69
+				}
70
+				$default_value                      = ModelDataTranslator::prepareFieldValueForJson(
71
+					$field_obj,
72
+					$field_obj->get_default_value(),
73
+					$this->getModelVersionInfo()->requestedVersion()
74
+				);
75
+				$field_json                         = [
76
+					'name'                => $field_name,
77
+					'nicename'            => wp_specialchars_decode($field_obj->get_nicename(), ENT_QUOTES),
78
+					'has_rendered_format' => $this->getModelVersionInfo()->fieldHasRenderedFormat($field_obj),
79
+					'has_pretty_format'   => $this->getModelVersionInfo()->fieldHasPrettyFormat($field_obj),
80
+					'type'                => str_replace('EE_', '', get_class($field_obj)),
81
+					'datatype'            => $datatype,
82
+					'nullable'            => $field_obj->is_nullable(),
83
+					'default'             => $default_value,
84
+					'table_alias'         => $field_obj->get_table_alias(),
85
+					'table_column'        => $field_obj->get_table_column(),
86
+				];
87
+				$fields_json[ $field_json['name'] ] = $field_json;
88
+			}
89
+			$fields_json                       = array_merge(
90
+				$fields_json,
91
+				$this->getModelVersionInfo()->extraResourcePropertiesForModel($model)
92
+			);
93
+			$response[ $model_name ]['fields'] = apply_filters(
94
+				'FHEE__Meta__handle_request_models_meta__fields',
95
+				$fields_json,
96
+				$model
97
+			);
98
+			$relations_json                    = [];
99
+			foreach ($model->relation_settings() as $relation_name => $relation_obj) {
100
+				$relation_json                    = [
101
+					'name'   => $relation_name,
102
+					'type'   => str_replace('EE_', '', get_class($relation_obj)),
103
+					'single' => $relation_obj instanceof EE_Belongs_To_Relation,
104
+				];
105
+				$relations_json[ $relation_name ] = $relation_json;
106
+			}
107
+			$response[ $model_name ]['relations'] = apply_filters(
108
+				'FHEE__Meta__handle_request_models_meta__relations',
109
+				$relations_json,
110
+				$model
111
+			);
112
+		}
113
+		return $response;
114
+	}
115 115
 
116 116
 
117
-    /**
118
-     * Adds EE metadata to the index
119
-     *
120
-     * @param WP_REST_Response $rest_response_obj
121
-     * @return WP_REST_Response
122
-     */
123
-    public static function filterEeMetadataIntoIndex(WP_REST_Response $rest_response_obj): WP_REST_Response
124
-    {
125
-        $response_data = $rest_response_obj->get_data();
126
-        $addons        = [];
127
-        foreach (EE_Registry::instance()->addons as $addon) {
128
-            $addon_json                    = [
129
-                'name'    => $addon->name(),
130
-                'version' => $addon->version(),
131
-            ];
132
-            $addons[ $addon_json['name'] ] = $addon_json;
133
-        }
134
-        $response_data['ee'] = [
135
-            'version'              => EEM_System_Status::instance()->get_ee_version(),
136
-            // @codingStandardsIgnoreStart
137
-            'documentation_url'    => 'https://github.com/eventespresso/event-espresso-core/tree/master/docs/C--REST-API',
138
-            // @codingStandardsIgnoreEnd
139
-            'addons'               => $addons,
140
-            'maintenance_mode'     => EE_Maintenance_Mode::instance()->real_level(),
141
-            'served_core_versions' => array_keys(EED_Core_Rest_Api::versions_served()),
142
-        ];
143
-        $rest_response_obj->set_data($response_data);
144
-        return $rest_response_obj;
145
-    }
117
+	/**
118
+	 * Adds EE metadata to the index
119
+	 *
120
+	 * @param WP_REST_Response $rest_response_obj
121
+	 * @return WP_REST_Response
122
+	 */
123
+	public static function filterEeMetadataIntoIndex(WP_REST_Response $rest_response_obj): WP_REST_Response
124
+	{
125
+		$response_data = $rest_response_obj->get_data();
126
+		$addons        = [];
127
+		foreach (EE_Registry::instance()->addons as $addon) {
128
+			$addon_json                    = [
129
+				'name'    => $addon->name(),
130
+				'version' => $addon->version(),
131
+			];
132
+			$addons[ $addon_json['name'] ] = $addon_json;
133
+		}
134
+		$response_data['ee'] = [
135
+			'version'              => EEM_System_Status::instance()->get_ee_version(),
136
+			// @codingStandardsIgnoreStart
137
+			'documentation_url'    => 'https://github.com/eventespresso/event-espresso-core/tree/master/docs/C--REST-API',
138
+			// @codingStandardsIgnoreEnd
139
+			'addons'               => $addons,
140
+			'maintenance_mode'     => EE_Maintenance_Mode::instance()->real_level(),
141
+			'served_core_versions' => array_keys(EED_Core_Rest_Api::versions_served()),
142
+		];
143
+		$rest_response_obj->set_data($response_data);
144
+		return $rest_response_obj;
145
+	}
146 146
 }
147 147
 
148 148
 
Please login to merge, or discard this patch.
Spacing   +11 added lines, -11 removed lines patch added patch discarded remove patch
@@ -67,12 +67,12 @@  discard block
 block discarded – undo
67 67
                 } else {
68 68
                     $datatype = 'String';
69 69
                 }
70
-                $default_value                      = ModelDataTranslator::prepareFieldValueForJson(
70
+                $default_value = ModelDataTranslator::prepareFieldValueForJson(
71 71
                     $field_obj,
72 72
                     $field_obj->get_default_value(),
73 73
                     $this->getModelVersionInfo()->requestedVersion()
74 74
                 );
75
-                $field_json                         = [
75
+                $field_json = [
76 76
                     'name'                => $field_name,
77 77
                     'nicename'            => wp_specialchars_decode($field_obj->get_nicename(), ENT_QUOTES),
78 78
                     'has_rendered_format' => $this->getModelVersionInfo()->fieldHasRenderedFormat($field_obj),
@@ -84,27 +84,27 @@  discard block
 block discarded – undo
84 84
                     'table_alias'         => $field_obj->get_table_alias(),
85 85
                     'table_column'        => $field_obj->get_table_column(),
86 86
                 ];
87
-                $fields_json[ $field_json['name'] ] = $field_json;
87
+                $fields_json[$field_json['name']] = $field_json;
88 88
             }
89
-            $fields_json                       = array_merge(
89
+            $fields_json = array_merge(
90 90
                 $fields_json,
91 91
                 $this->getModelVersionInfo()->extraResourcePropertiesForModel($model)
92 92
             );
93
-            $response[ $model_name ]['fields'] = apply_filters(
93
+            $response[$model_name]['fields'] = apply_filters(
94 94
                 'FHEE__Meta__handle_request_models_meta__fields',
95 95
                 $fields_json,
96 96
                 $model
97 97
             );
98
-            $relations_json                    = [];
98
+            $relations_json = [];
99 99
             foreach ($model->relation_settings() as $relation_name => $relation_obj) {
100
-                $relation_json                    = [
100
+                $relation_json = [
101 101
                     'name'   => $relation_name,
102 102
                     'type'   => str_replace('EE_', '', get_class($relation_obj)),
103 103
                     'single' => $relation_obj instanceof EE_Belongs_To_Relation,
104 104
                 ];
105
-                $relations_json[ $relation_name ] = $relation_json;
105
+                $relations_json[$relation_name] = $relation_json;
106 106
             }
107
-            $response[ $model_name ]['relations'] = apply_filters(
107
+            $response[$model_name]['relations'] = apply_filters(
108 108
                 'FHEE__Meta__handle_request_models_meta__relations',
109 109
                 $relations_json,
110 110
                 $model
@@ -125,11 +125,11 @@  discard block
 block discarded – undo
125 125
         $response_data = $rest_response_obj->get_data();
126 126
         $addons        = [];
127 127
         foreach (EE_Registry::instance()->addons as $addon) {
128
-            $addon_json                    = [
128
+            $addon_json = [
129 129
                 'name'    => $addon->name(),
130 130
                 'version' => $addon->version(),
131 131
             ];
132
-            $addons[ $addon_json['name'] ] = $addon_json;
132
+            $addons[$addon_json['name']] = $addon_json;
133 133
         }
134 134
         $response_data['ee'] = [
135 135
             'version'              => EEM_System_Status::instance()->get_ee_version(),
Please login to merge, or discard this patch.
core/libraries/rest_api/controllers/model/Read.php 2 patches
Indentation   +1644 added lines, -1644 removed lines patch added patch discarded remove patch
@@ -50,1648 +50,1648 @@
 block discarded – undo
50 50
  */
51 51
 class Read extends Base
52 52
 {
53
-    /**
54
-     * @var CalculatedModelFields
55
-     */
56
-    protected $fields_calculator;
57
-
58
-
59
-    /**
60
-     * Read constructor.
61
-     *
62
-     * @param CalculatedModelFields $fields_calculator
63
-     */
64
-    public function __construct(CalculatedModelFields $fields_calculator)
65
-    {
66
-        parent::__construct();
67
-        $this->fields_calculator = $fields_calculator;
68
-    }
69
-
70
-
71
-    /**
72
-     * @param string $version
73
-     * @return Read
74
-     * @since $VID:$
75
-     */
76
-    public static function getModelReadController(string $version): Read
77
-    {
78
-        /** @var Read $controller */
79
-        $controller = LoaderFactory::getLoader()->getNew(Read::class);
80
-        $controller->setRequestedVersion($version);
81
-        return $controller;
82
-    }
83
-
84
-
85
-    /**
86
-     * Handles requests to get all (or a filtered subset) of entities for a particular model
87
-     *
88
-     * @param WP_REST_Request $request
89
-     * @param string          $version
90
-     * @param string          $model_name
91
-     * @return WP_REST_Response
92
-     * @throws InvalidArgumentException
93
-     * @throws InvalidDataTypeException
94
-     * @throws InvalidInterfaceException
95
-     */
96
-    public static function handleRequestGetAll(
97
-        WP_REST_Request $request,
98
-        string $version,
99
-        string $model_name
100
-    ): WP_REST_Response {
101
-        $controller = Read::getModelReadController($version);
102
-        try {
103
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
104
-                return $controller->sendResponse(Read::endpointParsingError($model_name));
105
-            }
106
-            return $controller->sendResponse(
107
-                $controller->getEntitiesFromModel(
108
-                    $controller->getModelVersionInfo()->loadModel($model_name),
109
-                    $request
110
-                )
111
-            );
112
-        } catch (Exception $e) {
113
-            return $controller->sendResponse($e);
114
-        }
115
-    }
116
-
117
-
118
-    /**
119
-     * Prepares and returns schema for any OPTIONS request.
120
-     *
121
-     * @param string $version    The API endpoint version being used.
122
-     * @param string $model_name Something like `Event` or `Registration`
123
-     * @return array
124
-     * @throws InvalidArgumentException
125
-     * @throws InvalidDataTypeException
126
-     * @throws InvalidInterfaceException
127
-     */
128
-    public static function handleSchemaRequest(string $version, string $model_name): array
129
-    {
130
-        $controller = Read::getModelReadController($version);
131
-        try {
132
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
133
-                return [];
134
-            }
135
-            // get the model for this version
136
-            $model        = $controller->getModelVersionInfo()->loadModel($model_name);
137
-            $model_schema = new JsonModelSchema(
138
-                $model,
139
-                LoaderFactory::getLoader()->getShared('EventEspresso\core\libraries\rest_api\CalculatedModelFields')
140
-            );
141
-            return $model_schema->getModelSchemaForRelations(
142
-                $controller->getModelVersionInfo()->relationSettings($model),
143
-                $controller->customizeSchemaForRestResponse(
144
-                    $model,
145
-                    $model_schema->getModelSchemaForFields(
146
-                        $controller->getModelVersionInfo()->fieldsOnModelInThisVersion($model),
147
-                        $model_schema->getInitialSchemaStructure()
148
-                    )
149
-                )
150
-            );
151
-        } catch (Exception $e) {
152
-            return [];
153
-        }
154
-    }
155
-
156
-
157
-    /**
158
-     * This loops through each field in the given schema for the model and does the following:
159
-     * - add any extra fields that are REST API specific and related to existing fields.
160
-     * - transform default values into the correct format for a REST API response.
161
-     *
162
-     * @param EEM_Base $model
163
-     * @param array    $schema
164
-     * @return array  The final schema.
165
-     * @throws EE_Error
166
-     * @throws EE_Error
167
-     */
168
-    protected function customizeSchemaForRestResponse(EEM_Base $model, array $schema): array
169
-    {
170
-        foreach ($this->getModelVersionInfo()->fieldsOnModelInThisVersion($model) as $field_name => $field) {
171
-            $schema = $this->translateDefaultsForRestResponse(
172
-                $field_name,
173
-                $field,
174
-                $this->maybeAddExtraFieldsToSchema($field_name, $field, $schema)
175
-            );
176
-        }
177
-        return $schema;
178
-    }
179
-
180
-
181
-    /**
182
-     * This is used to ensure that the 'default' value set in the schema response is formatted correctly for the REST
183
-     * response.
184
-     *
185
-     * @param                      $field_name
186
-     * @param EE_Model_Field_Base  $field
187
-     * @param array                $schema
188
-     * @return array
189
-     * @throws RestException  if a default value has a PHP object, which we should never do
190
-     *                                  (but if we did, let's know about it ASAP, so let the exception bubble up)
191
-     * @throws EE_Error
192
-     *
193
-     */
194
-    protected function translateDefaultsForRestResponse($field_name, EE_Model_Field_Base $field, array $schema): array
195
-    {
196
-        if (isset($schema['properties'][ $field_name ]['default'])) {
197
-            if (is_array($schema['properties'][ $field_name ]['default'])) {
198
-                foreach ($schema['properties'][ $field_name ]['default'] as $default_key => $default_value) {
199
-                    if ($default_key === 'raw') {
200
-                        $schema['properties'][ $field_name ]['default'][ $default_key ] =
201
-                            ModelDataTranslator::prepareFieldValueForJson(
202
-                                $field,
203
-                                $default_value,
204
-                                $this->getModelVersionInfo()->requestedVersion()
205
-                            );
206
-                    }
207
-                }
208
-            } else {
209
-                $schema['properties'][ $field_name ]['default'] = ModelDataTranslator::prepareFieldValueForJson(
210
-                    $field,
211
-                    $schema['properties'][ $field_name ]['default'],
212
-                    $this->getModelVersionInfo()->requestedVersion()
213
-                );
214
-            }
215
-        }
216
-        return $schema;
217
-    }
218
-
219
-
220
-    /**
221
-     * Adds additional fields to the schema
222
-     * The REST API returns a GMT value field for each datetime field in the resource.  Thus the description about this
223
-     * needs to be added to the schema.
224
-     *
225
-     * @param                      $field_name
226
-     * @param EE_Model_Field_Base  $field
227
-     * @param array                $schema
228
-     * @return array
229
-     */
230
-    protected function maybeAddExtraFieldsToSchema($field_name, EE_Model_Field_Base $field, array $schema): array
231
-    {
232
-        if ($field instanceof EE_Datetime_Field) {
233
-            $schema['properties'][ $field_name . '_gmt' ] = $field->getSchema();
234
-            // modify the description
235
-            $schema['properties'][ $field_name . '_gmt' ]['description'] = sprintf(
236
-                esc_html__('%s - the value for this field is in GMT.', 'event_espresso'),
237
-                wp_specialchars_decode($field->get_nicename(), ENT_QUOTES)
238
-            );
239
-        }
240
-        return $schema;
241
-    }
242
-
243
-
244
-    /**
245
-     * Used to figure out the route from the request when a `WP_REST_Request` object is not available
246
-     *
247
-     * @return string
248
-     */
249
-    protected function getRouteFromRequest(): string
250
-    {
251
-        if (
252
-            isset($GLOBALS['wp'])
253
-            && $GLOBALS['wp'] instanceof WP
254
-            && isset($GLOBALS['wp']->query_vars['rest_route'])
255
-        ) {
256
-            return $GLOBALS['wp']->query_vars['rest_route'];
257
-        } else {
258
-            /** @var RequestInterface $request */
259
-            $request = LoaderFactory::getLoader()->getShared(RequestInterface::class);
260
-            return $request->serverParamIsSet('PATH_INFO')
261
-                ? $request->getServerParam('PATH_INFO')
262
-                : '/';
263
-        }
264
-    }
265
-
266
-
267
-    /**
268
-     * Gets a single entity related to the model indicated in the path and its id
269
-     *
270
-     * @param WP_REST_Request $request
271
-     * @param string          $version
272
-     * @param string          $model_name
273
-     * @return WP_REST_Response
274
-     * @throws InvalidDataTypeException
275
-     * @throws InvalidInterfaceException
276
-     * @throws InvalidArgumentException
277
-     */
278
-    public static function handleRequestGetOne(
279
-        WP_REST_Request $request,
280
-        string $version,
281
-        string $model_name
282
-    ): WP_REST_Response {
283
-        $controller = Read::getModelReadController($version);
284
-        try {
285
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
286
-                return $controller->sendResponse(Read::endpointParsingError($model_name));
287
-            }
288
-            return $controller->sendResponse(
289
-                $controller->getEntityFromModel(
290
-                    $controller->getModelVersionInfo()->loadModel($model_name),
291
-                    $request
292
-                )
293
-            );
294
-        } catch (Exception $e) {
295
-            return $controller->sendResponse($e);
296
-        }
297
-    }
298
-
299
-
300
-    /**
301
-     * Gets all the related entities (or if its a belongs-to relation just the one)
302
-     * to the item with the given id
303
-     *
304
-     * @param WP_REST_Request $request
305
-     * @param string          $version
306
-     * @param string          $model_name
307
-     * @param string          $related_model_name
308
-     * @return WP_REST_Response
309
-     * @throws InvalidDataTypeException
310
-     * @throws InvalidInterfaceException
311
-     * @throws InvalidArgumentException
312
-     */
313
-    public static function handleRequestGetRelated(
314
-        WP_REST_Request $request,
315
-        string $version,
316
-        string $model_name,
317
-        string $related_model_name
318
-    ): WP_REST_Response {
319
-        $controller = Read::getModelReadController($version);
320
-        try {
321
-            $main_model = $controller->validateModel($model_name);
322
-            $controller->validateModel($related_model_name);
323
-            return $controller->sendResponse(
324
-                $controller->getEntitiesFromRelation(
325
-                    $request->get_param('id'),
326
-                    $main_model->related_settings_for($related_model_name),
327
-                    $request
328
-                )
329
-            );
330
-        } catch (Exception $e) {
331
-            return $controller->sendResponse($e);
332
-        }
333
-    }
334
-
335
-
336
-    /**
337
-     * Gets a collection for the given model and filters
338
-     *
339
-     * @param EEM_Base        $model
340
-     * @param WP_REST_Request $request
341
-     * @return array
342
-     * @throws EE_Error
343
-     * @throws InvalidArgumentException
344
-     * @throws InvalidDataTypeException
345
-     * @throws InvalidInterfaceException
346
-     * @throws ReflectionException
347
-     * @throws RestException
348
-     */
349
-    public function getEntitiesFromModel(EEM_Base $model, WP_REST_Request $request): array
350
-    {
351
-        $query_params = $this->createModelQueryParams($model, $request->get_params());
352
-        if (! Capabilities::currentUserHasPartialAccessTo($model, $query_params['caps'])) {
353
-            $model_name_plural = EEH_Inflector::pluralize_and_lower($model->get_this_model_name());
354
-            throw new RestException(
355
-                sprintf('rest_%s_cannot_list', $model_name_plural),
356
-                sprintf(
357
-                    esc_html__('Sorry, you are not allowed to list %1$s. Missing permissions: %2$s', 'event_espresso'),
358
-                    $model_name_plural,
359
-                    Capabilities::getMissingPermissionsString($model, $query_params['caps'])
360
-                ),
361
-                ['status' => 403]
362
-            );
363
-        }
364
-        if (! $request->get_header('no_rest_headers')) {
365
-            $this->setHeadersFromQueryParams($model, $query_params);
366
-        }
367
-        /** @type array $results */
368
-        $results      = $model->get_all_wpdb_results($query_params);
369
-        $nice_results = [];
370
-        foreach ($results as $result) {
371
-            $nice_results[] = $this->createEntityFromWpdbResult(
372
-                $model,
373
-                $result,
374
-                $request
375
-            );
376
-        }
377
-        return $nice_results;
378
-    }
379
-
380
-
381
-    /**
382
-     * Gets the collection for given relation object
383
-     * The same as Read::get_entities_from_model(), except if the relation
384
-     * is a HABTM relation, in which case it merges any non-foreign-key fields from
385
-     * the join-model-object into the results
386
-     *
387
-     * @param array                  $primary_model_query_params  query params for finding the item from which
388
-     *                                                            relations will be based
389
-     * @param EE_Model_Relation_Base $relation
390
-     * @param WP_REST_Request        $request
391
-     * @return array
392
-     * @throws EE_Error
393
-     * @throws InvalidArgumentException
394
-     * @throws InvalidDataTypeException
395
-     * @throws InvalidInterfaceException
396
-     * @throws ReflectionException
397
-     * @throws RestException
398
-     * @throws ModelConfigurationException
399
-     */
400
-    protected function getEntitiesFromRelationUsingModelQueryParams(
401
-        array $primary_model_query_params,
402
-        EE_Model_Relation_Base $relation,
403
-        WP_REST_Request $request
404
-    ): array {
405
-        $context       = $this->validateContext($request->get_param('caps'));
406
-        $model         = $relation->get_this_model();
407
-        $related_model = $relation->get_other_model();
408
-        if (! isset($primary_model_query_params[0])) {
409
-            $primary_model_query_params[0] = [];
410
-        }
411
-        // check if they can access the 1st model object
412
-        $primary_model_query_params = [
413
-            0       => $primary_model_query_params[0],
414
-            'limit' => 1,
415
-        ];
416
-        if ($model instanceof EEM_Soft_Delete_Base) {
417
-            $primary_model_query_params = $model->alter_query_params_so_deleted_and_undeleted_items_included(
418
-                $primary_model_query_params
419
-            );
420
-        }
421
-        $restricted_query_params          = $primary_model_query_params;
422
-        $restricted_query_params['caps']  = $context;
423
-        $restricted_query_params['limit'] = 1;
424
-        $this->setDebugInfo('main model query params', $restricted_query_params);
425
-        $this->setDebugInfo('missing caps', Capabilities::getMissingPermissionsString($related_model, $context));
426
-        $primary_model_rows = $model->get_all_wpdb_results($restricted_query_params);
427
-        $primary_model_row  = null;
428
-        if (is_array($primary_model_rows)) {
429
-            $primary_model_row = reset($primary_model_rows);
430
-        }
431
-        if (
432
-            ! (
433
-                $primary_model_row
434
-                && Capabilities::currentUserHasPartialAccessTo($related_model, $context)
435
-            )
436
-        ) {
437
-            if ($relation instanceof EE_Belongs_To_Relation) {
438
-                $related_model_name_maybe_plural = strtolower($related_model->get_this_model_name());
439
-            } else {
440
-                $related_model_name_maybe_plural = EEH_Inflector::pluralize_and_lower(
441
-                    $related_model->get_this_model_name()
442
-                );
443
-            }
444
-            throw new RestException(
445
-                sprintf('rest_%s_cannot_list', $related_model_name_maybe_plural),
446
-                sprintf(
447
-                    esc_html__(
448
-                        'Sorry, you are not allowed to list %1$s related to %2$s. Missing permissions: %3$s',
449
-                        'event_espresso'
450
-                    ),
451
-                    $related_model_name_maybe_plural,
452
-                    $relation->get_this_model()->get_this_model_name(),
453
-                    implode(
454
-                        ',',
455
-                        array_keys(
456
-                            Capabilities::getMissingPermissions($related_model, $context)
457
-                        )
458
-                    )
459
-                ),
460
-                ['status' => 403]
461
-            );
462
-        }
463
-
464
-        $this->checkPassword(
465
-            $model,
466
-            $primary_model_row,
467
-            $restricted_query_params,
468
-            $request
469
-        );
470
-        $query_params = $this->createModelQueryParams($relation->get_other_model(), $request->get_params());
471
-        foreach ($primary_model_query_params[0] as $where_condition_key => $where_condition_value) {
472
-            $query_params[0][ $relation->get_this_model()->get_this_model_name()
473
-                              . '.'
474
-                              . $where_condition_key ] = $where_condition_value;
475
-        }
476
-        $query_params['default_where_conditions'] = 'none';
477
-        $query_params['caps']                     = $context;
478
-        if (! $request->get_header('no_rest_headers')) {
479
-            $this->setHeadersFromQueryParams($relation->get_other_model(), $query_params);
480
-        }
481
-        /** @type array $results */
482
-        $results      = $relation->get_other_model()->get_all_wpdb_results($query_params);
483
-        $nice_results = [];
484
-        foreach ($results as $result) {
485
-            $nice_result = $this->createEntityFromWpdbResult(
486
-                $relation->get_other_model(),
487
-                $result,
488
-                $request
489
-            );
490
-            if ($relation instanceof EE_HABTM_Relation) {
491
-                // put the unusual stuff (properties from the HABTM relation) first, and make sure
492
-                // if there are conflicts we prefer the properties from the main model
493
-                $join_model_result = $this->createEntityFromWpdbResult(
494
-                    $relation->get_join_model(),
495
-                    $result,
496
-                    $request
497
-                );
498
-                $joined_result     = array_merge($join_model_result, $nice_result);
499
-                // but keep the meta stuff from the main model
500
-                if (isset($nice_result['meta'])) {
501
-                    $joined_result['meta'] = $nice_result['meta'];
502
-                }
503
-                $nice_result = $joined_result;
504
-            }
505
-            $nice_results[] = $nice_result;
506
-        }
507
-        if ($relation instanceof EE_Belongs_To_Relation) {
508
-            return array_shift($nice_results);
509
-        } else {
510
-            return $nice_results;
511
-        }
512
-    }
513
-
514
-
515
-    /**
516
-     * Gets the collection for given relation object
517
-     * The same as Read::get_entities_from_model(), except if the relation
518
-     * is a HABTM relation, in which case it merges any non-foreign-key fields from
519
-     * the join-model-object into the results
520
-     *
521
-     * @param string                 $id the ID of the thing we are fetching related stuff from
522
-     * @param EE_Model_Relation_Base $relation
523
-     * @param WP_REST_Request        $request
524
-     * @return array
525
-     * @throws EE_Error
526
-     * @throws ReflectionException
527
-     */
528
-    public function getEntitiesFromRelation(
529
-        string $id,
530
-        EE_Model_Relation_Base $relation,
531
-        WP_REST_Request $request
532
-    ): array {
533
-        if (! $relation->get_this_model()->has_primary_key_field()) {
534
-            throw new EE_Error(
535
-                sprintf(
536
-                    esc_html__(
537
-                    // @codingStandardsIgnoreStart
538
-                        'Read::get_entities_from_relation should only be called from a model with a primary key, it was called from %1$s',
539
-                        // @codingStandardsIgnoreEnd
540
-                        'event_espresso'
541
-                    ),
542
-                    $relation->get_this_model()->get_this_model_name()
543
-                )
544
-            );
545
-        }
546
-        // can we edit that main item?
547
-        // if not, show nothing but an error
548
-        // otherwise, please proceed
549
-        return $this->getEntitiesFromRelationUsingModelQueryParams(
550
-            [
551
-                [
552
-                    $relation->get_this_model()->primary_key_name() => $id,
553
-                ],
554
-            ],
555
-            $relation,
556
-            $request
557
-        );
558
-    }
559
-
560
-
561
-    /**
562
-     * Sets the headers that are based on the model and query params,
563
-     * like the total records. This should only be called on the original request
564
-     * from the client, not on subsequent internal
565
-     *
566
-     * @param EEM_Base $model
567
-     * @param array    $query_params
568
-     * @return void
569
-     * @throws EE_Error
570
-     * @throws EE_Error
571
-     */
572
-    protected function setHeadersFromQueryParams(EEM_Base $model, array $query_params)
573
-    {
574
-        $this->setDebugInfo('model query params', $query_params);
575
-        $this->setDebugInfo(
576
-            'missing caps',
577
-            Capabilities::getMissingPermissionsString($model, $query_params['caps'])
578
-        );
579
-        // normally the limit to a 2-part array, where the 2nd item is the limit
580
-        if (! isset($query_params['limit'])) {
581
-            $query_params['limit'] = EED_Core_Rest_Api::get_default_query_limit();
582
-        }
583
-        if (is_array($query_params['limit'])) {
584
-            $limit_parts = $query_params['limit'];
585
-        } else {
586
-            $limit_parts = explode(',', $query_params['limit']);
587
-            if (count($limit_parts) == 1) {
588
-                $limit_parts = [0, $limit_parts[0]];
589
-            }
590
-        }
591
-        // remove the group by and having parts of the query, as those will
592
-        // make the sql query return an array of values, instead of just a single value
593
-        unset($query_params['group_by'], $query_params['having'], $query_params['limit']);
594
-        $count = $model->count($query_params, null, true);
595
-        $pages = $count / $limit_parts[1];
596
-        $this->setResponseHeader('Total', $count, false);
597
-        $this->setResponseHeader('PageSize', $limit_parts[1], false);
598
-        $this->setResponseHeader('TotalPages', ceil($pages), false);
599
-    }
600
-
601
-
602
-    /**
603
-     * Changes database results into REST API entities
604
-     *
605
-     * @param EEM_Base             $model
606
-     * @param array                $db_row     like results from $wpdb->get_results()
607
-     * @param WP_REST_Request|null $rest_request
608
-     * @param string|null          $deprecated no longer used
609
-     * @return array ready for being converted into json for sending to client
610
-     * @throws EE_Error
611
-     * @throws ReflectionException
612
-     * @throws RestException
613
-     * @throws RestPasswordIncorrectException
614
-     * @throws RestPasswordRequiredException
615
-     */
616
-    public function createEntityFromWpdbResult(
617
-        EEM_Base $model,
618
-        array $db_row,
619
-        ?WP_REST_Request $rest_request,
620
-        string $deprecated = null
621
-    ): array {
622
-        if (! $rest_request instanceof WP_REST_Request) {
623
-            // ok so this was called in the old style, where the 3rd arg was
624
-            // $include, and the 4th arg was $context
625
-            // now setup the request just to avoid fatal errors, although we won't be able
626
-            // to truly make use of it because it's kinda devoid of info
627
-            $rest_request = new WP_REST_Request();
628
-            $rest_request->set_param('include', $rest_request);
629
-            $rest_request->set_param('caps', $deprecated);
630
-        }
631
-        if ($rest_request->get_param('caps') == null) {
632
-            $rest_request->set_param('caps', EEM_Base::caps_read);
633
-        }
634
-        $current_user_full_access_to_entity = $model->currentUserCan(
635
-            EEM_Base::caps_read_admin,
636
-            $model->deduce_fields_n_values_from_cols_n_values($db_row)
637
-        );
638
-        $entity_array                       = $this->createBareEntityFromWpdbResults($model, $db_row);
639
-        $entity_array                       = $this->addExtraFields($model, $db_row, $entity_array);
640
-        $entity_array['_links']             = $this->getEntityLinks($model, $db_row, $entity_array);
641
-        // when it's a regular read request for a model with a password and the password wasn't provided
642
-        // remove the password protected fields
643
-        $has_protected_fields = false;
644
-        try {
645
-            $this->checkPassword(
646
-                $model,
647
-                $db_row,
648
-                $model->alter_query_params_to_restrict_by_ID(
649
-                    $model->get_index_primary_key_string(
650
-                        $model->deduce_fields_n_values_from_cols_n_values($db_row)
651
-                    )
652
-                ),
653
-                $rest_request
654
-            );
655
-        } catch (RestPasswordRequiredException $e) {
656
-            if ($model->hasPassword()) {
657
-                // just remove protected fields
658
-                $has_protected_fields = true;
659
-                $entity_array         = Capabilities::filterOutPasswordProtectedFields(
660
-                    $entity_array,
661
-                    $model,
662
-                    $this->getModelVersionInfo()
663
-                );
664
-            } else {
665
-                // that's a problem. None of this should be accessible if no password was provided
666
-                throw $e;
667
-            }
668
-        }
669
-
670
-        $entity_array['_calculated_fields'] =
671
-            $this->getEntityCalculations($model, $db_row, $rest_request, $has_protected_fields);
672
-        $entity_array                       = apply_filters(
673
-            'FHEE__Read__create_entity_from_wpdb_results__entity_before_including_requested_models',
674
-            $entity_array,
675
-            $model,
676
-            $rest_request->get_param('caps'),
677
-            $rest_request,
678
-            $this
679
-        );
680
-        // add an empty protected property for now. If it's still around after we remove everything the request didn't
681
-        // want, we'll populate it then. k?
682
-        $entity_array['_protected'] = [];
683
-        // remove any properties the request didn't want. This way _protected won't bother mentioning them
684
-        $entity_array = $this->includeOnlyRequestedProperties($model, $rest_request, $entity_array);
685
-        $entity_array =
686
-            $this->includeRequestedModels($model, $rest_request, $entity_array, $db_row, $has_protected_fields);
687
-        // if they still wanted the _protected property, add it.
688
-        if (isset($entity_array['_protected'])) {
689
-            $entity_array = $this->addProtectedProperty($model, $entity_array, $has_protected_fields);
690
-        }
691
-        $entity_array = apply_filters(
692
-            'FHEE__Read__create_entity_from_wpdb_results__entity_before_inaccessible_field_removal',
693
-            $entity_array,
694
-            $model,
695
-            $rest_request->get_param('caps'),
696
-            $rest_request,
697
-            $this
698
-        );
699
-        if (! $current_user_full_access_to_entity) {
700
-            $result_without_inaccessible_fields = Capabilities::filterOutInaccessibleEntityFields(
701
-                $entity_array,
702
-                $model,
703
-                $rest_request->get_param('caps'),
704
-                $this->getModelVersionInfo()
705
-            );
706
-        } else {
707
-            $result_without_inaccessible_fields = $entity_array;
708
-        }
709
-        $this->setDebugInfo(
710
-            'inaccessible fields',
711
-            array_keys(array_diff_key((array) $entity_array, (array) $result_without_inaccessible_fields))
712
-        );
713
-        return apply_filters(
714
-            'FHEE__Read__create_entity_from_wpdb_results__entity_return',
715
-            $result_without_inaccessible_fields,
716
-            $model,
717
-            $rest_request->get_param('caps')
718
-        );
719
-    }
720
-
721
-
722
-    /**
723
-     * Returns an array describing which fields can be protected, and which actually were removed this request
724
-     *
725
-     * @param EEM_Base $model
726
-     * @param array    $results_so_far
727
-     * @param bool     $protected
728
-     * @return array results
729
-     * @throws EE_Error
730
-     * @since 4.9.74.p
731
-     */
732
-    protected function addProtectedProperty(EEM_Base $model, array $results_so_far, bool $protected): array
733
-    {
734
-        if (! $model->hasPassword() || ! $protected) {
735
-            return $results_so_far;
736
-        }
737
-        $password_field  = $model->getPasswordField();
738
-        $all_protected   = array_merge(
739
-            [$password_field->get_name()],
740
-            $password_field->protectedFields()
741
-        );
742
-        $fields_included = array_keys($results_so_far);
743
-        $fields_included = array_intersect(
744
-            $all_protected,
745
-            $fields_included
746
-        );
747
-        foreach ($fields_included as $field_name) {
748
-            $results_so_far['_protected'][] = $field_name;
749
-        }
750
-        return $results_so_far;
751
-    }
752
-
753
-
754
-    /**
755
-     * Creates a REST entity array (JSON object we're going to return in the response, but
756
-     * for now still a PHP array, but soon enough we'll call json_encode on it, don't worry),
757
-     * from $wpdb->get_row( $sql, ARRAY_A)
758
-     *
759
-     * @param EEM_Base $model
760
-     * @param array    $db_row
761
-     * @return array entity mostly ready for converting to JSON and sending in the response
762
-     * @throws EE_Error
763
-     * @throws ReflectionException
764
-     * @throws RestException
765
-     */
766
-    protected function createBareEntityFromWpdbResults(EEM_Base $model, array $db_row): array
767
-    {
768
-        $result = $model->deduce_fields_n_values_from_cols_n_values($db_row);
769
-        $result = array_intersect_key(
770
-            $result,
771
-            $this->getModelVersionInfo()->fieldsOnModelInThisVersion($model)
772
-        );
773
-        // if this is a CPT, we need to set the global $post to it,
774
-        // otherwise shortcodes etc won't work properly while rendering it
775
-        if ($model instanceof EEM_CPT_Base) {
776
-            $do_chevy_shuffle = true;
777
-        } else {
778
-            $do_chevy_shuffle = false;
779
-        }
780
-        if ($do_chevy_shuffle) {
781
-            global $post;
782
-            $old_post = $post;
783
-            $post     = get_post($result[ $model->primary_key_name() ]);
784
-            if (! $post instanceof WP_Post) {
785
-                // well that's weird, because $result is what we JUST fetched from the database
786
-                throw new RestException(
787
-                    'error_fetching_post_from_database_results',
788
-                    esc_html__(
789
-                        'An item was retrieved from the database but it\'s not a WP_Post like it should be.',
790
-                        'event_espresso'
791
-                    )
792
-                );
793
-            }
794
-            $model_object_classname          = 'EE_' . $model->get_this_model_name();
795
-            $post->{$model_object_classname} = EE_Registry::instance()->load_class(
796
-                $model_object_classname,
797
-                $result,
798
-                false,
799
-                false
800
-            );
801
-        }
802
-        foreach ($result as $field_name => $field_value) {
803
-            $field_obj = $model->field_settings_for($field_name);
804
-            if ($this->isSubclassOfOne($field_obj, $this->getModelVersionInfo()->fieldsIgnored())) {
805
-                unset($result[ $field_name ]);
806
-            } elseif (
807
-                $this->isSubclassOfOne(
808
-                    $field_obj,
809
-                    $this->getModelVersionInfo()->fieldsThatHaveRenderedFormat()
810
-                )
811
-            ) {
812
-                $result[ $field_name ] = [
813
-                    'raw'      => $this->prepareFieldObjValueForJson($field_obj, $field_value),
814
-                    'rendered' => $this->prepareFieldObjValueForJson($field_obj, $field_value, 'pretty'),
815
-                ];
816
-            } elseif (
817
-                $this->isSubclassOfOne(
818
-                    $field_obj,
819
-                    $this->getModelVersionInfo()->fieldsThatHavePrettyFormat()
820
-                )
821
-            ) {
822
-                $result[ $field_name ] = [
823
-                    'raw'    => $this->prepareFieldObjValueForJson($field_obj, $field_value),
824
-                    'pretty' => $this->prepareFieldObjValueForJson($field_obj, $field_value, 'pretty'),
825
-                ];
826
-            } elseif ($field_obj instanceof EE_Datetime_Field) {
827
-                $field_value = $field_obj->prepare_for_set_from_db($field_value);
828
-                // if the value is null, but we're not supposed to permit null, then set to the field's default
829
-                if (is_null($field_value)) {
830
-                    $field_value = $field_obj->getDefaultDateTimeObj();
831
-                }
832
-                if (is_null($field_value)) {
833
-                    $gmt_date = $local_date = ModelDataTranslator::prepareFieldValuesForJson(
834
-                        $field_obj,
835
-                        $field_value,
836
-                        $this->getModelVersionInfo()->requestedVersion()
837
-                    );
838
-                } else {
839
-                    $timezone = $field_value->getTimezone();
840
-                    EEH_DTT_Helper::setTimezone($field_value, new DateTimeZone('UTC'));
841
-                    $gmt_date = ModelDataTranslator::prepareFieldValuesForJson(
842
-                        $field_obj,
843
-                        $field_value,
844
-                        $this->getModelVersionInfo()->requestedVersion()
845
-                    );
846
-                    EEH_DTT_Helper::setTimezone($field_value, $timezone);
847
-                    $local_date = ModelDataTranslator::prepareFieldValuesForJson(
848
-                        $field_obj,
849
-                        $field_value,
850
-                        $this->getModelVersionInfo()->requestedVersion()
851
-                    );
852
-                }
853
-                $result[ $field_name . '_gmt' ] = $gmt_date;
854
-                $result[ $field_name ]          = $local_date;
855
-            } else {
856
-                $result[ $field_name ] = $this->prepareFieldObjValueForJson($field_obj, $field_value);
857
-            }
858
-        }
859
-        if ($do_chevy_shuffle) {
860
-            $post = $old_post;
861
-        }
862
-        return $result;
863
-    }
864
-
865
-
866
-    /**
867
-     * Takes a value all the way from the DB representation, to the model object's representation, to the
868
-     * user-facing PHP representation, to the REST API representation. (Assumes you've already taken from the DB
869
-     * representation using $field_obj->prepare_for_set_from_db())
870
-     *
871
-     * @param EE_Model_Field_Base $field_obj
872
-     * @param mixed               $value  as it's stored on a model object
873
-     * @param string              $format valid values are 'normal' (default), 'pretty', 'datetime_obj'
874
-     * @return mixed
875
-     * @throws RestException if $value contains a PHP object
876
-     * @throws EE_Error
877
-     */
878
-    protected function prepareFieldObjValueForJson(
879
-        EE_Model_Field_Base $field_obj,
880
-        $value,
881
-        string $format = 'normal'
882
-    ) {
883
-        $value = $field_obj->prepare_for_set_from_db($value);
884
-        switch ($format) {
885
-            case 'pretty':
886
-                $value = $field_obj->prepare_for_pretty_echoing($value);
887
-                break;
888
-            case 'normal':
889
-            default:
890
-                $value = $field_obj->prepare_for_get($value);
891
-                break;
892
-        }
893
-        return ModelDataTranslator::prepareFieldValuesForJson(
894
-            $field_obj,
895
-            $value,
896
-            $this->getModelVersionInfo()->requestedVersion()
897
-        );
898
-    }
899
-
900
-
901
-    /**
902
-     * Adds a few extra fields to the entity response
903
-     *
904
-     * @param EEM_Base $model
905
-     * @param array    $db_row
906
-     * @param array    $entity_array
907
-     * @return array modified entity
908
-     * @throws EE_Error
909
-     * @throws EE_Error
910
-     */
911
-    protected function addExtraFields(EEM_Base $model, array $db_row, array $entity_array): array
912
-    {
913
-        if ($model instanceof EEM_CPT_Base) {
914
-            $entity_array['link'] = get_permalink($db_row[ $model->get_primary_key_field()->get_qualified_column() ]);
915
-        }
916
-        return $entity_array;
917
-    }
918
-
919
-
920
-    /**
921
-     * Gets links we want to add to the response
922
-     *
923
-     * @param EEM_Base        $model
924
-     * @param array           $db_row
925
-     * @param array           $entity_array
926
-     * @return array the _links item in the entity
927
-     * @throws EE_Error
928
-     * @throws EE_Error
929
-     * @global WP_REST_Server $wp_rest_server
930
-     */
931
-    protected function getEntityLinks(EEM_Base $model, array $db_row, array $entity_array): array
932
-    {
933
-        // add basic links
934
-        $links = [];
935
-        if ($model->has_primary_key_field()) {
936
-            $links['self'] = [
937
-                [
938
-                    'href' => $this->getVersionedLinkTo(
939
-                        EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
940
-                        . '/'
941
-                        . $entity_array[ $model->primary_key_name() ]
942
-                    ),
943
-                ],
944
-            ];
945
-        }
946
-        $links['collection'] = [
947
-            [
948
-                'href' => $this->getVersionedLinkTo(
949
-                    EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
950
-                ),
951
-            ],
952
-        ];
953
-        // add links to related models
954
-        if ($model->has_primary_key_field()) {
955
-            foreach ($this->getModelVersionInfo()->relationSettings($model) as $relation_name => $relation_obj) {
956
-                $related_model_part                                                      =
957
-                    Read::getRelatedEntityName($relation_name, $relation_obj);
958
-                $links[ EED_Core_Rest_Api::ee_api_link_namespace . $related_model_part ] = [
959
-                    [
960
-                        'href'   => $this->getVersionedLinkTo(
961
-                            EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
962
-                            . '/'
963
-                            . $entity_array[ $model->primary_key_name() ]
964
-                            . '/'
965
-                            . $related_model_part
966
-                        ),
967
-                        'single' => $relation_obj instanceof EE_Belongs_To_Relation,
968
-                    ],
969
-                ];
970
-            }
971
-        }
972
-        return $links;
973
-    }
974
-
975
-
976
-    /**
977
-     * Adds the included models indicated in the request to the entity provided
978
-     *
979
-     * @param EEM_Base        $model
980
-     * @param WP_REST_Request $rest_request
981
-     * @param array           $entity_array
982
-     * @param array           $db_row
983
-     * @param boolean         $included_items_protected if the original item is password protected, don't include any
984
-     *                                                  related models.
985
-     * @return array the modified entity
986
-     * @throws EE_Error
987
-     * @throws ReflectionException
988
-     */
989
-    protected function includeRequestedModels(
990
-        EEM_Base $model,
991
-        WP_REST_Request $rest_request,
992
-        array $entity_array,
993
-        array $db_row = [],
994
-        bool $included_items_protected = false
995
-    ): array {
996
-        // if $db_row not included, hope the entity array has what we need
997
-        if (! $db_row) {
998
-            $db_row = $entity_array;
999
-        }
1000
-        $relation_settings = $this->getModelVersionInfo()->relationSettings($model);
1001
-        foreach ($relation_settings as $relation_name => $relation_obj) {
1002
-            $related_fields_to_include   = $this->explodeAndGetItemsPrefixedWith(
1003
-                $rest_request->get_param('include'),
1004
-                $relation_name
1005
-            );
1006
-            $related_fields_to_calculate = $this->explodeAndGetItemsPrefixedWith(
1007
-                $rest_request->get_param('calculate'),
1008
-                $relation_name
1009
-            );
1010
-            // did they specify they wanted to include a related model, or
1011
-            // specific fields from a related model?
1012
-            // or did they specify to calculate a field from a related model?
1013
-            if ($related_fields_to_include || $related_fields_to_calculate) {
1014
-                // if so, we should include at least some part of the related model
1015
-                $pretend_related_request = new WP_REST_Request();
1016
-                $pretend_related_request->set_query_params(
1017
-                    [
1018
-                        'caps'      => $rest_request->get_param('caps'),
1019
-                        'include'   => $related_fields_to_include,
1020
-                        'calculate' => $related_fields_to_calculate,
1021
-                        'password'  => $rest_request->get_param('password'),
1022
-                    ]
1023
-                );
1024
-                $pretend_related_request->add_header('no_rest_headers', true);
1025
-                $primary_model_query_params = $model->alter_query_params_to_restrict_by_ID(
1026
-                    $model->get_index_primary_key_string(
1027
-                        $model->deduce_fields_n_values_from_cols_n_values($db_row)
1028
-                    )
1029
-                );
1030
-                if (! $included_items_protected) {
1031
-                    try {
1032
-                        $related_results = $this->getEntitiesFromRelationUsingModelQueryParams(
1033
-                            $primary_model_query_params,
1034
-                            $relation_obj,
1035
-                            $pretend_related_request
1036
-                        );
1037
-                    } catch (RestException $e) {
1038
-                        $related_results = new WP_Error('entity_relations_error', $e->getMessage());
1039
-                    }
1040
-                } else {
1041
-                    // they're protected, hide them.
1042
-                    $related_results              = null;
1043
-                    $entity_array['_protected'][] = Read::getRelatedEntityName($relation_name, $relation_obj);
1044
-                }
1045
-                if ($related_results instanceof WP_Error) {
1046
-                    $related_results = $relation_obj instanceof EE_Belongs_To_Relation
1047
-                        ? null
1048
-                        : [];
1049
-                }
1050
-                $entity_array[ Read::getRelatedEntityName($relation_name, $relation_obj) ] = $related_results;
1051
-            }
1052
-        }
1053
-        return $entity_array;
1054
-    }
1055
-
1056
-
1057
-    /**
1058
-     * If the user has requested only specific properties (including meta properties like _links or _protected)
1059
-     * remove everything else.
1060
-     *
1061
-     * @param EEM_Base        $model
1062
-     * @param WP_REST_Request $rest_request
1063
-     * @param                 $entity_array
1064
-     * @return array
1065
-     * @throws EE_Error
1066
-     * @since 4.9.74.p
1067
-     */
1068
-    protected function includeOnlyRequestedProperties(
1069
-        EEM_Base $model,
1070
-        WP_REST_Request $rest_request,
1071
-        $entity_array
1072
-    ): array {
1073
-        $includes_for_this_model = $this->explodeAndGetItemsPrefixedWith($rest_request->get_param('include'), '');
1074
-        $includes_for_this_model = $this->removeModelNamesFromArray($includes_for_this_model);
1075
-        // if they passed in * or didn't specify any includes, return everything
1076
-        if (
1077
-            ! in_array('*', $includes_for_this_model)
1078
-            && ! empty($includes_for_this_model)
1079
-        ) {
1080
-            if ($model->has_primary_key_field()) {
1081
-                // always include the primary key. ya just gotta know that at least
1082
-                $includes_for_this_model[] = $model->primary_key_name();
1083
-            }
1084
-            if ($this->explodeAndGetItemsPrefixedWith($rest_request->get_param('calculate'), '')) {
1085
-                $includes_for_this_model[] = '_calculated_fields';
1086
-            }
1087
-            $entity_array = array_intersect_key($entity_array, array_flip($includes_for_this_model));
1088
-        }
1089
-        return $entity_array;
1090
-    }
1091
-
1092
-
1093
-    /**
1094
-     * Returns a new array with all the names of models removed. Eg
1095
-     * array( 'Event', 'Datetime.*', 'foobar' ) would become array( 'Datetime.*', 'foobar' )
1096
-     *
1097
-     * @param array $model_names
1098
-     * @return array
1099
-     */
1100
-    private function removeModelNamesFromArray(array $model_names): array
1101
-    {
1102
-        return array_diff($model_names, array_keys(EE_Registry::instance()->non_abstract_db_models));
1103
-    }
1104
-
1105
-
1106
-    /**
1107
-     * Gets the calculated fields for the response
1108
-     *
1109
-     * @param EEM_Base        $model
1110
-     * @param array           $wpdb_row
1111
-     * @param WP_REST_Request $rest_request
1112
-     * @param boolean         $row_is_protected whether this row is password protected or not
1113
-     * @return stdClass the _calculations item in the entity
1114
-     * @throws RestException if a default value has a PHP object, which should never do (and if we
1115
-     * @throws EE_Error
1116
-     *                                          did, let's know about it ASAP, so let the exception bubble up)
1117
-     */
1118
-    protected function getEntityCalculations(
1119
-        EEM_Base $model,
1120
-        array $wpdb_row,
1121
-        WP_REST_Request $rest_request,
1122
-        bool $row_is_protected = false
1123
-    ): stdClass {
1124
-        $calculated_fields = $this->explodeAndGetItemsPrefixedWith(
1125
-            $rest_request->get_param('calculate'),
1126
-            ''
1127
-        );
1128
-        // note: setting calculate=* doesn't do anything
1129
-        $calculated_fields_to_return = new stdClass();
1130
-        $protected_fields            = [];
1131
-        foreach ($calculated_fields as $field_to_calculate) {
1132
-            try {
1133
-                // it's password protected, so they shouldn't be able to read this. Remove the value
1134
-                $schema = $this->fields_calculator->getJsonSchemaForModel($model);
1135
-                if (
1136
-                    $row_is_protected
1137
-                    && isset($schema['properties'][ $field_to_calculate ]['protected'])
1138
-                    && $schema['properties'][ $field_to_calculate ]['protected']
1139
-                ) {
1140
-                    $calculated_value   = null;
1141
-                    $protected_fields[] = $field_to_calculate;
1142
-                    if ($schema['properties'][ $field_to_calculate ]['type']) {
1143
-                        switch ($schema['properties'][ $field_to_calculate ]['type']) {
1144
-                            case 'boolean':
1145
-                                $calculated_value = false;
1146
-                                break;
1147
-                            case 'integer':
1148
-                                $calculated_value = 0;
1149
-                                break;
1150
-                            case 'string':
1151
-                                $calculated_value = '';
1152
-                                break;
1153
-                            case 'array':
1154
-                                $calculated_value = [];
1155
-                                break;
1156
-                            case 'object':
1157
-                                $calculated_value = new stdClass();
1158
-                                break;
1159
-                        }
1160
-                    }
1161
-                } else {
1162
-                    $calculated_value = ModelDataTranslator::prepareFieldValueForJson(
1163
-                        null,
1164
-                        $this->fields_calculator->retrieveCalculatedFieldValue(
1165
-                            $model,
1166
-                            $field_to_calculate,
1167
-                            $wpdb_row,
1168
-                            $rest_request,
1169
-                            $this
1170
-                        ),
1171
-                        $this->getModelVersionInfo()->requestedVersion()
1172
-                    );
1173
-                }
1174
-                $calculated_fields_to_return->{$field_to_calculate} = $calculated_value;
1175
-            } catch (RestException $e) {
1176
-                // if we don't have permission to read it, just leave it out. but let devs know about the problem
1177
-                $this->setResponseHeader(
1178
-                    'Notices-Field-Calculation-Errors['
1179
-                    . $e->getStringCode()
1180
-                    . ']['
1181
-                    . $model->get_this_model_name()
1182
-                    . ']['
1183
-                    . $field_to_calculate
1184
-                    . ']',
1185
-                    $e->getMessage()
1186
-                );
1187
-            }
1188
-        }
1189
-        $calculated_fields_to_return->_protected = $protected_fields;
1190
-        return $calculated_fields_to_return;
1191
-    }
1192
-
1193
-
1194
-    /**
1195
-     * Gets the full URL to the resource, taking the requested version into account
1196
-     *
1197
-     * @param string $link_part_after_version_and_slash eg "events/10/datetimes"
1198
-     * @return string url eg "http://mysite.com/wp-json/ee/v4.6/events/10/datetimes"
1199
-     * @throws EE_Error
1200
-     * @throws EE_Error
1201
-     */
1202
-    public function getVersionedLinkTo(string $link_part_after_version_and_slash): string
1203
-    {
1204
-        return rest_url(
1205
-            EED_Core_Rest_Api::get_versioned_route_to(
1206
-                $link_part_after_version_and_slash,
1207
-                $this->getModelVersionInfo()->requestedVersion()
1208
-            )
1209
-        );
1210
-    }
1211
-
1212
-
1213
-    /**
1214
-     * Gets the correct lowercase name for the relation in the API according
1215
-     * to the relation's type
1216
-     *
1217
-     * @param string                 $relation_name
1218
-     * @param EE_Model_Relation_Base $relation_obj
1219
-     * @return string
1220
-     */
1221
-    public static function getRelatedEntityName(string $relation_name, EE_Model_Relation_Base $relation_obj): string
1222
-    {
1223
-        if ($relation_obj instanceof EE_Belongs_To_Relation) {
1224
-            return strtolower($relation_name);
1225
-        } else {
1226
-            return EEH_Inflector::pluralize_and_lower($relation_name);
1227
-        }
1228
-    }
1229
-
1230
-
1231
-    /**
1232
-     * Gets the one model object with the specified id for the specified model
1233
-     *
1234
-     * @param EEM_Base        $model
1235
-     * @param WP_REST_Request $request
1236
-     * @return array
1237
-     * @throws EE_Error
1238
-     * @throws EE_Error
1239
-     * @throws ReflectionException
1240
-     */
1241
-    public function getEntityFromModel(EEM_Base $model, WP_REST_Request $request): array
1242
-    {
1243
-        $context = $this->validateContext($request->get_param('caps'));
1244
-        return $this->getOneOrReportPermissionError($model, $request, $context);
1245
-    }
1246
-
1247
-
1248
-    /**
1249
-     * If a context is provided which isn't valid, maybe it was added in a future
1250
-     * version so just treat it as a default read
1251
-     *
1252
-     * @param string $context
1253
-     * @return string array key of EEM_Base::cap_contexts_to_cap_action_map()
1254
-     */
1255
-    public function validateContext(string $context): string
1256
-    {
1257
-        if (! $context) {
1258
-            $context = EEM_Base::caps_read;
1259
-        }
1260
-        $valid_contexts = EEM_Base::valid_cap_contexts();
1261
-        if (in_array($context, $valid_contexts)) {
1262
-            return $context;
1263
-        } else {
1264
-            return EEM_Base::caps_read;
1265
-        }
1266
-    }
1267
-
1268
-
1269
-    /**
1270
-     * Verifies the passed in value is an allowable default where conditions value.
1271
-     *
1272
-     * @param $default_query_params
1273
-     * @return string
1274
-     */
1275
-    public function validateDefaultQueryParams($default_query_params): string
1276
-    {
1277
-        $valid_default_where_conditions_for_api_calls = [
1278
-            EEM_Base::default_where_conditions_all,
1279
-            EEM_Base::default_where_conditions_minimum_all,
1280
-            EEM_Base::default_where_conditions_minimum_others,
1281
-        ];
1282
-        if (! $default_query_params) {
1283
-            $default_query_params = EEM_Base::default_where_conditions_all;
1284
-        }
1285
-        if (
1286
-            in_array(
1287
-                $default_query_params,
1288
-                $valid_default_where_conditions_for_api_calls,
1289
-                true
1290
-            )
1291
-        ) {
1292
-            return $default_query_params;
1293
-        }
1294
-        return EEM_Base::default_where_conditions_all;
1295
-    }
1296
-
1297
-
1298
-    /**
1299
-     * Translates API filter get parameter into model query params @param EEM_Base $model
1300
-     *
1301
-     * @param array $query_params
1302
-     * @return array model query params (@see
1303
-     *               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions)
1304
-     *               or FALSE to indicate that absolutely no results should be returned
1305
-     * @throws EE_Error
1306
-     * @throws RestException
1307
-     * @see
1308
-     *               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions.
1309
-     *               Note: right now the query parameter keys for fields (and related fields) can be left as-is, but
1310
-     *               it's quite possible this will change someday. Also, this method's contents might be candidate for
1311
-     *               moving to Model_Data_Translator
1312
-     *
1313
-     */
1314
-    public function createModelQueryParams(EEM_Base $model, array $query_params): array
1315
-    {
1316
-        $model_query_params = [];
1317
-        if (isset($query_params['where'])) {
1318
-            $model_query_params[0] = ModelDataTranslator::prepareConditionsQueryParamsForModels(
1319
-                $query_params['where'],
1320
-                $model,
1321
-                $this->getModelVersionInfo()->requestedVersion()
1322
-            );
1323
-        }
1324
-        if (isset($query_params['order_by'])) {
1325
-            $order_by = $query_params['order_by'];
1326
-        } elseif (isset($query_params['orderby'])) {
1327
-            $order_by = $query_params['orderby'];
1328
-        } else {
1329
-            $order_by = null;
1330
-        }
1331
-        if ($order_by !== null) {
1332
-            if (is_array($order_by)) {
1333
-                $order_by = ModelDataTranslator::prepareFieldNamesInArrayKeysFromJson($order_by);
1334
-            } else {
1335
-                // it's a single item
1336
-                $order_by = ModelDataTranslator::prepareFieldNameFromJson($order_by);
1337
-            }
1338
-            $model_query_params['order_by'] = $order_by;
1339
-        }
1340
-        if (isset($query_params['group_by'])) {
1341
-            $group_by = $query_params['group_by'];
1342
-        } elseif (isset($query_params['groupby'])) {
1343
-            $group_by = $query_params['groupby'];
1344
-        } else {
1345
-            $group_by = array_keys($model->get_combined_primary_key_fields());
1346
-        }
1347
-        // make sure they're all real names
1348
-        if (is_array($group_by)) {
1349
-            $group_by = ModelDataTranslator::prepareFieldNamesFromJson($group_by);
1350
-        }
1351
-        if ($group_by !== null) {
1352
-            $model_query_params['group_by'] = $group_by;
1353
-        }
1354
-        if (isset($query_params['having'])) {
1355
-            $model_query_params['having'] = ModelDataTranslator::prepareConditionsQueryParamsForModels(
1356
-                $query_params['having'],
1357
-                $model,
1358
-                $this->getModelVersionInfo()->requestedVersion()
1359
-            );
1360
-        }
1361
-        if (isset($query_params['order'])) {
1362
-            $model_query_params['order'] = $query_params['order'];
1363
-        }
1364
-        if (isset($query_params['mine'])) {
1365
-            $model_query_params = $model->alter_query_params_to_only_include_mine($model_query_params);
1366
-        }
1367
-        if (isset($query_params['limit'])) {
1368
-            // limit should be either a string like '23' or '23,43', or an array with two items in it
1369
-            if (! is_array($query_params['limit'])) {
1370
-                $limit_array = explode(',', (string) $query_params['limit']);
1371
-            } else {
1372
-                $limit_array = $query_params['limit'];
1373
-            }
1374
-            $sanitized_limit = [];
1375
-            foreach ($limit_array as $limit_part) {
1376
-                if ($this->debug_mode && (! is_numeric($limit_part) || count($sanitized_limit) > 2)) {
1377
-                    throw new EE_Error(
1378
-                        sprintf(
1379
-                            esc_html__(
1380
-                            // @codingStandardsIgnoreStart
1381
-                                'An invalid limit filter was provided. It was: %s. If the EE4 JSON REST API weren\'t in debug mode, this message would not appear.',
1382
-                                // @codingStandardsIgnoreEnd
1383
-                                'event_espresso'
1384
-                            ),
1385
-                            wp_json_encode($query_params['limit'])
1386
-                        )
1387
-                    );
1388
-                }
1389
-                $sanitized_limit[] = (int) $limit_part;
1390
-            }
1391
-            $model_query_params['limit'] = implode(',', $sanitized_limit);
1392
-        } else {
1393
-            $model_query_params['limit'] = EED_Core_Rest_Api::get_default_query_limit();
1394
-        }
1395
-        if (isset($query_params['caps'])) {
1396
-            $model_query_params['caps'] = $this->validateContext($query_params['caps']);
1397
-        } else {
1398
-            $model_query_params['caps'] = EEM_Base::caps_read;
1399
-        }
1400
-        if (isset($query_params['default_where_conditions'])) {
1401
-            $model_query_params['default_where_conditions'] = $this->validateDefaultQueryParams(
1402
-                $query_params['default_where_conditions']
1403
-            );
1404
-        }
1405
-        // if this is a model protected by a password on another model, exclude the password protected
1406
-        // entities by default. But if they passed in a password, try to show them all. If the password is wrong,
1407
-        // though, they'll get an error (see Read::createEntityFromWpdbResult() which calls Read::checkPassword)
1408
-        if (
1409
-            ! $model->hasPassword()
1410
-            && $model->restrictedByRelatedModelPassword()
1411
-            && $model_query_params['caps'] === EEM_Base::caps_read
1412
-        ) {
1413
-            if (empty($query_params['password'])) {
1414
-                $model_query_params['exclude_protected'] = true;
1415
-            }
1416
-        }
1417
-
1418
-        return apply_filters('FHEE__Read__create_model_query_params', $model_query_params, $query_params, $model);
1419
-    }
1420
-
1421
-
1422
-    /**
1423
-     * Changes the REST-style query params for use in the models
1424
-     *
1425
-     * @param EEM_Base $model
1426
-     * @param array    $query_params sub-array from @see EEM_Base::get_all()
1427
-     * @return array
1428
-     * @deprecated
1429
-     */
1430
-    public function prepareRestQueryParamsKeyForModels(EEM_Base $model, array $query_params): array
1431
-    {
1432
-        $model_ready_query_params = [];
1433
-        foreach ($query_params as $key => $value) {
1434
-            $model_ready_query_params[ $key ] = is_array($value)
1435
-                ? $this->prepareRestQueryParamsKeyForModels($model, $value)
1436
-                : $value;
1437
-        }
1438
-        return $model_ready_query_params;
1439
-    }
1440
-
1441
-
1442
-    /**
1443
-     * @param EEM_Base $model
1444
-     * @param array    $query_params
1445
-     * @return array
1446
-     * @deprecated instead use ModelDataTranslator::prepareFieldValuesFromJson()
1447
-     */
1448
-    public function prepareRestQueryParamsValuesForModels(EEM_Base $model, array $query_params): array
1449
-    {
1450
-        $model_ready_query_params = [];
1451
-        foreach ($query_params as $key => $value) {
1452
-            if (is_array($value)) {
1453
-                $model_ready_query_params[ $key ] = $this->prepareRestQueryParamsValuesForModels($model, $value);
1454
-            } else {
1455
-                $model_ready_query_params[ $key ] = $value;
1456
-            }
1457
-        }
1458
-        return $model_ready_query_params;
1459
-    }
1460
-
1461
-
1462
-    /**
1463
-     * Explodes the string on commas, and only returns items with $prefix followed by a period.
1464
-     * If no prefix is specified, returns items with no period.
1465
-     *
1466
-     * @param string|array $string_to_explode eg "jibba,jabba, blah, blah, blah" or array('jibba', 'jabba' )
1467
-     * @param string       $prefix            "Event" or "foobar"
1468
-     * @return array $string_to_exploded exploded on COMMAS, and if a prefix was specified
1469
-     *                                        we only return strings starting with that and a period; if no prefix was
1470
-     *                                        specified we return all items containing NO periods
1471
-     */
1472
-    public function explodeAndGetItemsPrefixedWith($string_to_explode, string $prefix): array
1473
-    {
1474
-        if (is_string($string_to_explode)) {
1475
-            $exploded_contents = explode(',', $string_to_explode);
1476
-        } elseif (is_array($string_to_explode)) {
1477
-            $exploded_contents = $string_to_explode;
1478
-        } else {
1479
-            $exploded_contents = [];
1480
-        }
1481
-        // if the string was empty, we want an empty array
1482
-        $exploded_contents    = array_filter($exploded_contents);
1483
-        $contents_with_prefix = [];
1484
-        foreach ($exploded_contents as $item) {
1485
-            $item = trim($item);
1486
-            // if no prefix was provided, so we look for items with no "." in them
1487
-            if (! $prefix) {
1488
-                // does this item have a period?
1489
-                if (strpos($item, '.') === false) {
1490
-                    // if not, then its what we're looking for
1491
-                    $contents_with_prefix[] = $item;
1492
-                }
1493
-            } elseif (strpos($item, $prefix . '.') === 0) {
1494
-                // this item has the prefix and a period, grab it
1495
-                $contents_with_prefix[] = substr(
1496
-                    $item,
1497
-                    strpos($item, $prefix . '.') + strlen($prefix . '.')
1498
-                );
1499
-            } elseif ($item === $prefix) {
1500
-                // this item is JUST the prefix
1501
-                // so let's grab everything after, which is a blank string
1502
-                $contents_with_prefix[] = '';
1503
-            }
1504
-        }
1505
-        return $contents_with_prefix;
1506
-    }
1507
-
1508
-
1509
-    /**
1510
-     * @param array|string $include_string @see Read:handle_request_get_all
1511
-     * @param string|null  $model_name
1512
-     * @return array of fields for this model. If $model_name is provided, then
1513
-     *                                     the fields for that model, with the model's name removed from each.
1514
-     *                                     If $include_string was blank or '*' returns an empty array
1515
-     * @throws EE_Error
1516
-     * @throws EE_Error
1517
-     * @deprecated since 4.8.36.rc.001 You should instead use Read::explode_and_get_items_prefixed_with.
1518
-     *                                     Deprecated because its return values were really quite confusing- sometimes
1519
-     *                                     it returned an empty array (when the include string was blank or '*') or
1520
-     *                                     sometimes it returned array('*') (when you provided a model and a model of
1521
-     *                                     that kind was found). Parses the $include_string so we fetch all the field
1522
-     *                                     names relating to THIS model
1523
-     *                                     (ie have NO period in them), or for the provided model (ie start with the
1524
-     *                                     model name and then a period).
1525
-     */
1526
-    public function extractIncludesForThisModel($include_string, string $model_name = ''): array
1527
-    {
1528
-        if (is_array($include_string)) {
1529
-            $include_string = implode(',', $include_string);
1530
-        }
1531
-        if ($include_string === '*' || $include_string === '') {
1532
-            return [];
1533
-        }
1534
-        $includes                    = explode(',', $include_string);
1535
-        $extracted_fields_to_include = [];
1536
-        if ($model_name) {
1537
-            foreach ($includes as $field_to_include) {
1538
-                $field_to_include = trim($field_to_include);
1539
-                if (strpos($field_to_include, $model_name . '.') === 0) {
1540
-                    // found the model name at the exact start
1541
-                    $field_sans_model_name         = str_replace($model_name . '.', '', $field_to_include);
1542
-                    $extracted_fields_to_include[] = $field_sans_model_name;
1543
-                } elseif ($field_to_include == $model_name) {
1544
-                    $extracted_fields_to_include[] = '*';
1545
-                }
1546
-            }
1547
-        } else {
1548
-            // look for ones with no period
1549
-            foreach ($includes as $field_to_include) {
1550
-                $field_to_include = trim($field_to_include);
1551
-                if (
1552
-                    strpos($field_to_include, '.') === false
1553
-                    && ! $this->getModelVersionInfo()->isModelNameInThisVersion($field_to_include)
1554
-                ) {
1555
-                    $extracted_fields_to_include[] = $field_to_include;
1556
-                }
1557
-            }
1558
-        }
1559
-        return $extracted_fields_to_include;
1560
-    }
1561
-
1562
-
1563
-    /**
1564
-     * Gets the single item using the model according to the request in the context given, otherwise
1565
-     * returns that it's inaccessible to the current user
1566
-     *
1567
-     * @param EEM_Base        $model
1568
-     * @param WP_REST_Request $request
1569
-     * @param null            $context
1570
-     * @return array
1571
-     * @throws EE_Error
1572
-     * @throws ReflectionException
1573
-     */
1574
-    public function getOneOrReportPermissionError(EEM_Base $model, WP_REST_Request $request, $context = null): array
1575
-    {
1576
-        $query_params = [[$model->primary_key_name() => $request->get_param('id')], 'limit' => 1];
1577
-        if ($model instanceof EEM_Soft_Delete_Base) {
1578
-            $query_params = $model->alter_query_params_so_deleted_and_undeleted_items_included($query_params);
1579
-        }
1580
-        $restricted_query_params         = $query_params;
1581
-        $restricted_query_params['caps'] = $context;
1582
-        $this->setDebugInfo('model query params', $restricted_query_params);
1583
-        $model_rows = $model->get_all_wpdb_results($restricted_query_params);
1584
-        if (! empty($model_rows)) {
1585
-            return $this->createEntityFromWpdbResult(
1586
-                $model,
1587
-                reset($model_rows),
1588
-                $request
1589
-            );
1590
-        } else {
1591
-            // ok let's test to see if we WOULD have found it, had we not had restrictions from missing capabilities
1592
-            $lowercase_model_name = strtolower($model->get_this_model_name());
1593
-            if ($model->exists($query_params)) {
1594
-                // you got shafted- it existed but we didn't want to tell you!
1595
-                throw new RestException(
1596
-                    'rest_user_cannot_' . $context,
1597
-                    sprintf(
1598
-                        esc_html__('Sorry, you cannot %1$s this %2$s. Missing permissions are: %3$s', 'event_espresso'),
1599
-                        $context,
1600
-                        $lowercase_model_name,
1601
-                        Capabilities::getMissingPermissionsString(
1602
-                            $model,
1603
-                            $context
1604
-                        )
1605
-                    ),
1606
-                    ['status' => 403]
1607
-                );
1608
-            } else {
1609
-                // it's not you. It just doesn't exist
1610
-                throw new RestException(
1611
-                    sprintf('rest_%s_invalid_id', $lowercase_model_name),
1612
-                    sprintf(esc_html__('Invalid %s ID.', 'event_espresso'), $lowercase_model_name),
1613
-                    ['status' => 404]
1614
-                );
1615
-            }
1616
-        }
1617
-    }
1618
-
1619
-
1620
-    /**
1621
-     * Checks that if this content requires a password to be read, that it's been provided and is correct.
1622
-     *
1623
-     * @param EEM_Base        $model
1624
-     * @param array           $model_row
1625
-     * @param array           $query_params Adds 'default_where_conditions' => 'minimum'
1626
-     *                                      to ensure we don't confuse trashed with password protected.
1627
-     * @param WP_REST_Request $request
1628
-     * @throws EE_Error
1629
-     * @throws InvalidArgumentException
1630
-     * @throws InvalidDataTypeException
1631
-     * @throws InvalidInterfaceException
1632
-     * @throws RestPasswordRequiredException
1633
-     * @throws RestPasswordIncorrectException
1634
-     * @throws ModelConfigurationException
1635
-     * @throws ReflectionException
1636
-     * @since 4.9.74.p
1637
-     */
1638
-    protected function checkPassword(EEM_Base $model, array $model_row, array $query_params, WP_REST_Request $request)
1639
-    {
1640
-        $query_params['default_where_conditions'] = 'minimum';
1641
-        // stuff is only "protected" for front-end requests. Elsewhere, you either get full permission to access the object
1642
-        // or you don't.
1643
-        $request_caps = $request->get_param('caps');
1644
-        if (isset($request_caps) && $request_caps !== EEM_Base::caps_read) {
1645
-            return;
1646
-        }
1647
-        // if this entity requires a password, they better give it and it better be right!
1648
-        if (
1649
-            $model->hasPassword()
1650
-            && $model_row[ $model->getPasswordField()->get_qualified_column() ] !== ''
1651
-        ) {
1652
-            if (empty($request['password'])) {
1653
-                throw new RestPasswordRequiredException();
1654
-            }
1655
-            if (
1656
-                ! hash_equals(
1657
-                    $model_row[ $model->getPasswordField()->get_qualified_column() ],
1658
-                    $request['password']
1659
-                )
1660
-            ) {
1661
-                throw new RestPasswordIncorrectException();
1662
-            }
1663
-        } elseif (
1664
-            // wait! maybe this content is password protected
1665
-            $model->restrictedByRelatedModelPassword()
1666
-            && $request->get_param('caps') === EEM_Base::caps_read
1667
-        ) {
1668
-            $password_supplied = $request->get_param('password');
1669
-            if (empty($password_supplied)) {
1670
-                $query_params['exclude_protected'] = true;
1671
-                if (! $model->exists($query_params)) {
1672
-                    throw new RestPasswordRequiredException();
1673
-                }
1674
-            } else {
1675
-                $query_params[0][ $model->modelChainAndPassword() ] = $password_supplied;
1676
-                if (! $model->exists($query_params)) {
1677
-                    throw new RestPasswordIncorrectException();
1678
-                }
1679
-            }
1680
-        }
1681
-    }
1682
-
1683
-
1684
-    private static function endpointParsingError(string $model_name): WP_Error
1685
-    {
1686
-        return new WP_Error(
1687
-            'endpoint_parsing_error',
1688
-            sprintf(
1689
-                esc_html__(
1690
-                    'There is no model for endpoint %s. Please contact event espresso support',
1691
-                    'event_espresso'
1692
-                ),
1693
-                $model_name
1694
-            )
1695
-        );
1696
-    }
53
+	/**
54
+	 * @var CalculatedModelFields
55
+	 */
56
+	protected $fields_calculator;
57
+
58
+
59
+	/**
60
+	 * Read constructor.
61
+	 *
62
+	 * @param CalculatedModelFields $fields_calculator
63
+	 */
64
+	public function __construct(CalculatedModelFields $fields_calculator)
65
+	{
66
+		parent::__construct();
67
+		$this->fields_calculator = $fields_calculator;
68
+	}
69
+
70
+
71
+	/**
72
+	 * @param string $version
73
+	 * @return Read
74
+	 * @since $VID:$
75
+	 */
76
+	public static function getModelReadController(string $version): Read
77
+	{
78
+		/** @var Read $controller */
79
+		$controller = LoaderFactory::getLoader()->getNew(Read::class);
80
+		$controller->setRequestedVersion($version);
81
+		return $controller;
82
+	}
83
+
84
+
85
+	/**
86
+	 * Handles requests to get all (or a filtered subset) of entities for a particular model
87
+	 *
88
+	 * @param WP_REST_Request $request
89
+	 * @param string          $version
90
+	 * @param string          $model_name
91
+	 * @return WP_REST_Response
92
+	 * @throws InvalidArgumentException
93
+	 * @throws InvalidDataTypeException
94
+	 * @throws InvalidInterfaceException
95
+	 */
96
+	public static function handleRequestGetAll(
97
+		WP_REST_Request $request,
98
+		string $version,
99
+		string $model_name
100
+	): WP_REST_Response {
101
+		$controller = Read::getModelReadController($version);
102
+		try {
103
+			if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
104
+				return $controller->sendResponse(Read::endpointParsingError($model_name));
105
+			}
106
+			return $controller->sendResponse(
107
+				$controller->getEntitiesFromModel(
108
+					$controller->getModelVersionInfo()->loadModel($model_name),
109
+					$request
110
+				)
111
+			);
112
+		} catch (Exception $e) {
113
+			return $controller->sendResponse($e);
114
+		}
115
+	}
116
+
117
+
118
+	/**
119
+	 * Prepares and returns schema for any OPTIONS request.
120
+	 *
121
+	 * @param string $version    The API endpoint version being used.
122
+	 * @param string $model_name Something like `Event` or `Registration`
123
+	 * @return array
124
+	 * @throws InvalidArgumentException
125
+	 * @throws InvalidDataTypeException
126
+	 * @throws InvalidInterfaceException
127
+	 */
128
+	public static function handleSchemaRequest(string $version, string $model_name): array
129
+	{
130
+		$controller = Read::getModelReadController($version);
131
+		try {
132
+			if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
133
+				return [];
134
+			}
135
+			// get the model for this version
136
+			$model        = $controller->getModelVersionInfo()->loadModel($model_name);
137
+			$model_schema = new JsonModelSchema(
138
+				$model,
139
+				LoaderFactory::getLoader()->getShared('EventEspresso\core\libraries\rest_api\CalculatedModelFields')
140
+			);
141
+			return $model_schema->getModelSchemaForRelations(
142
+				$controller->getModelVersionInfo()->relationSettings($model),
143
+				$controller->customizeSchemaForRestResponse(
144
+					$model,
145
+					$model_schema->getModelSchemaForFields(
146
+						$controller->getModelVersionInfo()->fieldsOnModelInThisVersion($model),
147
+						$model_schema->getInitialSchemaStructure()
148
+					)
149
+				)
150
+			);
151
+		} catch (Exception $e) {
152
+			return [];
153
+		}
154
+	}
155
+
156
+
157
+	/**
158
+	 * This loops through each field in the given schema for the model and does the following:
159
+	 * - add any extra fields that are REST API specific and related to existing fields.
160
+	 * - transform default values into the correct format for a REST API response.
161
+	 *
162
+	 * @param EEM_Base $model
163
+	 * @param array    $schema
164
+	 * @return array  The final schema.
165
+	 * @throws EE_Error
166
+	 * @throws EE_Error
167
+	 */
168
+	protected function customizeSchemaForRestResponse(EEM_Base $model, array $schema): array
169
+	{
170
+		foreach ($this->getModelVersionInfo()->fieldsOnModelInThisVersion($model) as $field_name => $field) {
171
+			$schema = $this->translateDefaultsForRestResponse(
172
+				$field_name,
173
+				$field,
174
+				$this->maybeAddExtraFieldsToSchema($field_name, $field, $schema)
175
+			);
176
+		}
177
+		return $schema;
178
+	}
179
+
180
+
181
+	/**
182
+	 * This is used to ensure that the 'default' value set in the schema response is formatted correctly for the REST
183
+	 * response.
184
+	 *
185
+	 * @param                      $field_name
186
+	 * @param EE_Model_Field_Base  $field
187
+	 * @param array                $schema
188
+	 * @return array
189
+	 * @throws RestException  if a default value has a PHP object, which we should never do
190
+	 *                                  (but if we did, let's know about it ASAP, so let the exception bubble up)
191
+	 * @throws EE_Error
192
+	 *
193
+	 */
194
+	protected function translateDefaultsForRestResponse($field_name, EE_Model_Field_Base $field, array $schema): array
195
+	{
196
+		if (isset($schema['properties'][ $field_name ]['default'])) {
197
+			if (is_array($schema['properties'][ $field_name ]['default'])) {
198
+				foreach ($schema['properties'][ $field_name ]['default'] as $default_key => $default_value) {
199
+					if ($default_key === 'raw') {
200
+						$schema['properties'][ $field_name ]['default'][ $default_key ] =
201
+							ModelDataTranslator::prepareFieldValueForJson(
202
+								$field,
203
+								$default_value,
204
+								$this->getModelVersionInfo()->requestedVersion()
205
+							);
206
+					}
207
+				}
208
+			} else {
209
+				$schema['properties'][ $field_name ]['default'] = ModelDataTranslator::prepareFieldValueForJson(
210
+					$field,
211
+					$schema['properties'][ $field_name ]['default'],
212
+					$this->getModelVersionInfo()->requestedVersion()
213
+				);
214
+			}
215
+		}
216
+		return $schema;
217
+	}
218
+
219
+
220
+	/**
221
+	 * Adds additional fields to the schema
222
+	 * The REST API returns a GMT value field for each datetime field in the resource.  Thus the description about this
223
+	 * needs to be added to the schema.
224
+	 *
225
+	 * @param                      $field_name
226
+	 * @param EE_Model_Field_Base  $field
227
+	 * @param array                $schema
228
+	 * @return array
229
+	 */
230
+	protected function maybeAddExtraFieldsToSchema($field_name, EE_Model_Field_Base $field, array $schema): array
231
+	{
232
+		if ($field instanceof EE_Datetime_Field) {
233
+			$schema['properties'][ $field_name . '_gmt' ] = $field->getSchema();
234
+			// modify the description
235
+			$schema['properties'][ $field_name . '_gmt' ]['description'] = sprintf(
236
+				esc_html__('%s - the value for this field is in GMT.', 'event_espresso'),
237
+				wp_specialchars_decode($field->get_nicename(), ENT_QUOTES)
238
+			);
239
+		}
240
+		return $schema;
241
+	}
242
+
243
+
244
+	/**
245
+	 * Used to figure out the route from the request when a `WP_REST_Request` object is not available
246
+	 *
247
+	 * @return string
248
+	 */
249
+	protected function getRouteFromRequest(): string
250
+	{
251
+		if (
252
+			isset($GLOBALS['wp'])
253
+			&& $GLOBALS['wp'] instanceof WP
254
+			&& isset($GLOBALS['wp']->query_vars['rest_route'])
255
+		) {
256
+			return $GLOBALS['wp']->query_vars['rest_route'];
257
+		} else {
258
+			/** @var RequestInterface $request */
259
+			$request = LoaderFactory::getLoader()->getShared(RequestInterface::class);
260
+			return $request->serverParamIsSet('PATH_INFO')
261
+				? $request->getServerParam('PATH_INFO')
262
+				: '/';
263
+		}
264
+	}
265
+
266
+
267
+	/**
268
+	 * Gets a single entity related to the model indicated in the path and its id
269
+	 *
270
+	 * @param WP_REST_Request $request
271
+	 * @param string          $version
272
+	 * @param string          $model_name
273
+	 * @return WP_REST_Response
274
+	 * @throws InvalidDataTypeException
275
+	 * @throws InvalidInterfaceException
276
+	 * @throws InvalidArgumentException
277
+	 */
278
+	public static function handleRequestGetOne(
279
+		WP_REST_Request $request,
280
+		string $version,
281
+		string $model_name
282
+	): WP_REST_Response {
283
+		$controller = Read::getModelReadController($version);
284
+		try {
285
+			if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
286
+				return $controller->sendResponse(Read::endpointParsingError($model_name));
287
+			}
288
+			return $controller->sendResponse(
289
+				$controller->getEntityFromModel(
290
+					$controller->getModelVersionInfo()->loadModel($model_name),
291
+					$request
292
+				)
293
+			);
294
+		} catch (Exception $e) {
295
+			return $controller->sendResponse($e);
296
+		}
297
+	}
298
+
299
+
300
+	/**
301
+	 * Gets all the related entities (or if its a belongs-to relation just the one)
302
+	 * to the item with the given id
303
+	 *
304
+	 * @param WP_REST_Request $request
305
+	 * @param string          $version
306
+	 * @param string          $model_name
307
+	 * @param string          $related_model_name
308
+	 * @return WP_REST_Response
309
+	 * @throws InvalidDataTypeException
310
+	 * @throws InvalidInterfaceException
311
+	 * @throws InvalidArgumentException
312
+	 */
313
+	public static function handleRequestGetRelated(
314
+		WP_REST_Request $request,
315
+		string $version,
316
+		string $model_name,
317
+		string $related_model_name
318
+	): WP_REST_Response {
319
+		$controller = Read::getModelReadController($version);
320
+		try {
321
+			$main_model = $controller->validateModel($model_name);
322
+			$controller->validateModel($related_model_name);
323
+			return $controller->sendResponse(
324
+				$controller->getEntitiesFromRelation(
325
+					$request->get_param('id'),
326
+					$main_model->related_settings_for($related_model_name),
327
+					$request
328
+				)
329
+			);
330
+		} catch (Exception $e) {
331
+			return $controller->sendResponse($e);
332
+		}
333
+	}
334
+
335
+
336
+	/**
337
+	 * Gets a collection for the given model and filters
338
+	 *
339
+	 * @param EEM_Base        $model
340
+	 * @param WP_REST_Request $request
341
+	 * @return array
342
+	 * @throws EE_Error
343
+	 * @throws InvalidArgumentException
344
+	 * @throws InvalidDataTypeException
345
+	 * @throws InvalidInterfaceException
346
+	 * @throws ReflectionException
347
+	 * @throws RestException
348
+	 */
349
+	public function getEntitiesFromModel(EEM_Base $model, WP_REST_Request $request): array
350
+	{
351
+		$query_params = $this->createModelQueryParams($model, $request->get_params());
352
+		if (! Capabilities::currentUserHasPartialAccessTo($model, $query_params['caps'])) {
353
+			$model_name_plural = EEH_Inflector::pluralize_and_lower($model->get_this_model_name());
354
+			throw new RestException(
355
+				sprintf('rest_%s_cannot_list', $model_name_plural),
356
+				sprintf(
357
+					esc_html__('Sorry, you are not allowed to list %1$s. Missing permissions: %2$s', 'event_espresso'),
358
+					$model_name_plural,
359
+					Capabilities::getMissingPermissionsString($model, $query_params['caps'])
360
+				),
361
+				['status' => 403]
362
+			);
363
+		}
364
+		if (! $request->get_header('no_rest_headers')) {
365
+			$this->setHeadersFromQueryParams($model, $query_params);
366
+		}
367
+		/** @type array $results */
368
+		$results      = $model->get_all_wpdb_results($query_params);
369
+		$nice_results = [];
370
+		foreach ($results as $result) {
371
+			$nice_results[] = $this->createEntityFromWpdbResult(
372
+				$model,
373
+				$result,
374
+				$request
375
+			);
376
+		}
377
+		return $nice_results;
378
+	}
379
+
380
+
381
+	/**
382
+	 * Gets the collection for given relation object
383
+	 * The same as Read::get_entities_from_model(), except if the relation
384
+	 * is a HABTM relation, in which case it merges any non-foreign-key fields from
385
+	 * the join-model-object into the results
386
+	 *
387
+	 * @param array                  $primary_model_query_params  query params for finding the item from which
388
+	 *                                                            relations will be based
389
+	 * @param EE_Model_Relation_Base $relation
390
+	 * @param WP_REST_Request        $request
391
+	 * @return array
392
+	 * @throws EE_Error
393
+	 * @throws InvalidArgumentException
394
+	 * @throws InvalidDataTypeException
395
+	 * @throws InvalidInterfaceException
396
+	 * @throws ReflectionException
397
+	 * @throws RestException
398
+	 * @throws ModelConfigurationException
399
+	 */
400
+	protected function getEntitiesFromRelationUsingModelQueryParams(
401
+		array $primary_model_query_params,
402
+		EE_Model_Relation_Base $relation,
403
+		WP_REST_Request $request
404
+	): array {
405
+		$context       = $this->validateContext($request->get_param('caps'));
406
+		$model         = $relation->get_this_model();
407
+		$related_model = $relation->get_other_model();
408
+		if (! isset($primary_model_query_params[0])) {
409
+			$primary_model_query_params[0] = [];
410
+		}
411
+		// check if they can access the 1st model object
412
+		$primary_model_query_params = [
413
+			0       => $primary_model_query_params[0],
414
+			'limit' => 1,
415
+		];
416
+		if ($model instanceof EEM_Soft_Delete_Base) {
417
+			$primary_model_query_params = $model->alter_query_params_so_deleted_and_undeleted_items_included(
418
+				$primary_model_query_params
419
+			);
420
+		}
421
+		$restricted_query_params          = $primary_model_query_params;
422
+		$restricted_query_params['caps']  = $context;
423
+		$restricted_query_params['limit'] = 1;
424
+		$this->setDebugInfo('main model query params', $restricted_query_params);
425
+		$this->setDebugInfo('missing caps', Capabilities::getMissingPermissionsString($related_model, $context));
426
+		$primary_model_rows = $model->get_all_wpdb_results($restricted_query_params);
427
+		$primary_model_row  = null;
428
+		if (is_array($primary_model_rows)) {
429
+			$primary_model_row = reset($primary_model_rows);
430
+		}
431
+		if (
432
+			! (
433
+				$primary_model_row
434
+				&& Capabilities::currentUserHasPartialAccessTo($related_model, $context)
435
+			)
436
+		) {
437
+			if ($relation instanceof EE_Belongs_To_Relation) {
438
+				$related_model_name_maybe_plural = strtolower($related_model->get_this_model_name());
439
+			} else {
440
+				$related_model_name_maybe_plural = EEH_Inflector::pluralize_and_lower(
441
+					$related_model->get_this_model_name()
442
+				);
443
+			}
444
+			throw new RestException(
445
+				sprintf('rest_%s_cannot_list', $related_model_name_maybe_plural),
446
+				sprintf(
447
+					esc_html__(
448
+						'Sorry, you are not allowed to list %1$s related to %2$s. Missing permissions: %3$s',
449
+						'event_espresso'
450
+					),
451
+					$related_model_name_maybe_plural,
452
+					$relation->get_this_model()->get_this_model_name(),
453
+					implode(
454
+						',',
455
+						array_keys(
456
+							Capabilities::getMissingPermissions($related_model, $context)
457
+						)
458
+					)
459
+				),
460
+				['status' => 403]
461
+			);
462
+		}
463
+
464
+		$this->checkPassword(
465
+			$model,
466
+			$primary_model_row,
467
+			$restricted_query_params,
468
+			$request
469
+		);
470
+		$query_params = $this->createModelQueryParams($relation->get_other_model(), $request->get_params());
471
+		foreach ($primary_model_query_params[0] as $where_condition_key => $where_condition_value) {
472
+			$query_params[0][ $relation->get_this_model()->get_this_model_name()
473
+							  . '.'
474
+							  . $where_condition_key ] = $where_condition_value;
475
+		}
476
+		$query_params['default_where_conditions'] = 'none';
477
+		$query_params['caps']                     = $context;
478
+		if (! $request->get_header('no_rest_headers')) {
479
+			$this->setHeadersFromQueryParams($relation->get_other_model(), $query_params);
480
+		}
481
+		/** @type array $results */
482
+		$results      = $relation->get_other_model()->get_all_wpdb_results($query_params);
483
+		$nice_results = [];
484
+		foreach ($results as $result) {
485
+			$nice_result = $this->createEntityFromWpdbResult(
486
+				$relation->get_other_model(),
487
+				$result,
488
+				$request
489
+			);
490
+			if ($relation instanceof EE_HABTM_Relation) {
491
+				// put the unusual stuff (properties from the HABTM relation) first, and make sure
492
+				// if there are conflicts we prefer the properties from the main model
493
+				$join_model_result = $this->createEntityFromWpdbResult(
494
+					$relation->get_join_model(),
495
+					$result,
496
+					$request
497
+				);
498
+				$joined_result     = array_merge($join_model_result, $nice_result);
499
+				// but keep the meta stuff from the main model
500
+				if (isset($nice_result['meta'])) {
501
+					$joined_result['meta'] = $nice_result['meta'];
502
+				}
503
+				$nice_result = $joined_result;
504
+			}
505
+			$nice_results[] = $nice_result;
506
+		}
507
+		if ($relation instanceof EE_Belongs_To_Relation) {
508
+			return array_shift($nice_results);
509
+		} else {
510
+			return $nice_results;
511
+		}
512
+	}
513
+
514
+
515
+	/**
516
+	 * Gets the collection for given relation object
517
+	 * The same as Read::get_entities_from_model(), except if the relation
518
+	 * is a HABTM relation, in which case it merges any non-foreign-key fields from
519
+	 * the join-model-object into the results
520
+	 *
521
+	 * @param string                 $id the ID of the thing we are fetching related stuff from
522
+	 * @param EE_Model_Relation_Base $relation
523
+	 * @param WP_REST_Request        $request
524
+	 * @return array
525
+	 * @throws EE_Error
526
+	 * @throws ReflectionException
527
+	 */
528
+	public function getEntitiesFromRelation(
529
+		string $id,
530
+		EE_Model_Relation_Base $relation,
531
+		WP_REST_Request $request
532
+	): array {
533
+		if (! $relation->get_this_model()->has_primary_key_field()) {
534
+			throw new EE_Error(
535
+				sprintf(
536
+					esc_html__(
537
+					// @codingStandardsIgnoreStart
538
+						'Read::get_entities_from_relation should only be called from a model with a primary key, it was called from %1$s',
539
+						// @codingStandardsIgnoreEnd
540
+						'event_espresso'
541
+					),
542
+					$relation->get_this_model()->get_this_model_name()
543
+				)
544
+			);
545
+		}
546
+		// can we edit that main item?
547
+		// if not, show nothing but an error
548
+		// otherwise, please proceed
549
+		return $this->getEntitiesFromRelationUsingModelQueryParams(
550
+			[
551
+				[
552
+					$relation->get_this_model()->primary_key_name() => $id,
553
+				],
554
+			],
555
+			$relation,
556
+			$request
557
+		);
558
+	}
559
+
560
+
561
+	/**
562
+	 * Sets the headers that are based on the model and query params,
563
+	 * like the total records. This should only be called on the original request
564
+	 * from the client, not on subsequent internal
565
+	 *
566
+	 * @param EEM_Base $model
567
+	 * @param array    $query_params
568
+	 * @return void
569
+	 * @throws EE_Error
570
+	 * @throws EE_Error
571
+	 */
572
+	protected function setHeadersFromQueryParams(EEM_Base $model, array $query_params)
573
+	{
574
+		$this->setDebugInfo('model query params', $query_params);
575
+		$this->setDebugInfo(
576
+			'missing caps',
577
+			Capabilities::getMissingPermissionsString($model, $query_params['caps'])
578
+		);
579
+		// normally the limit to a 2-part array, where the 2nd item is the limit
580
+		if (! isset($query_params['limit'])) {
581
+			$query_params['limit'] = EED_Core_Rest_Api::get_default_query_limit();
582
+		}
583
+		if (is_array($query_params['limit'])) {
584
+			$limit_parts = $query_params['limit'];
585
+		} else {
586
+			$limit_parts = explode(',', $query_params['limit']);
587
+			if (count($limit_parts) == 1) {
588
+				$limit_parts = [0, $limit_parts[0]];
589
+			}
590
+		}
591
+		// remove the group by and having parts of the query, as those will
592
+		// make the sql query return an array of values, instead of just a single value
593
+		unset($query_params['group_by'], $query_params['having'], $query_params['limit']);
594
+		$count = $model->count($query_params, null, true);
595
+		$pages = $count / $limit_parts[1];
596
+		$this->setResponseHeader('Total', $count, false);
597
+		$this->setResponseHeader('PageSize', $limit_parts[1], false);
598
+		$this->setResponseHeader('TotalPages', ceil($pages), false);
599
+	}
600
+
601
+
602
+	/**
603
+	 * Changes database results into REST API entities
604
+	 *
605
+	 * @param EEM_Base             $model
606
+	 * @param array                $db_row     like results from $wpdb->get_results()
607
+	 * @param WP_REST_Request|null $rest_request
608
+	 * @param string|null          $deprecated no longer used
609
+	 * @return array ready for being converted into json for sending to client
610
+	 * @throws EE_Error
611
+	 * @throws ReflectionException
612
+	 * @throws RestException
613
+	 * @throws RestPasswordIncorrectException
614
+	 * @throws RestPasswordRequiredException
615
+	 */
616
+	public function createEntityFromWpdbResult(
617
+		EEM_Base $model,
618
+		array $db_row,
619
+		?WP_REST_Request $rest_request,
620
+		string $deprecated = null
621
+	): array {
622
+		if (! $rest_request instanceof WP_REST_Request) {
623
+			// ok so this was called in the old style, where the 3rd arg was
624
+			// $include, and the 4th arg was $context
625
+			// now setup the request just to avoid fatal errors, although we won't be able
626
+			// to truly make use of it because it's kinda devoid of info
627
+			$rest_request = new WP_REST_Request();
628
+			$rest_request->set_param('include', $rest_request);
629
+			$rest_request->set_param('caps', $deprecated);
630
+		}
631
+		if ($rest_request->get_param('caps') == null) {
632
+			$rest_request->set_param('caps', EEM_Base::caps_read);
633
+		}
634
+		$current_user_full_access_to_entity = $model->currentUserCan(
635
+			EEM_Base::caps_read_admin,
636
+			$model->deduce_fields_n_values_from_cols_n_values($db_row)
637
+		);
638
+		$entity_array                       = $this->createBareEntityFromWpdbResults($model, $db_row);
639
+		$entity_array                       = $this->addExtraFields($model, $db_row, $entity_array);
640
+		$entity_array['_links']             = $this->getEntityLinks($model, $db_row, $entity_array);
641
+		// when it's a regular read request for a model with a password and the password wasn't provided
642
+		// remove the password protected fields
643
+		$has_protected_fields = false;
644
+		try {
645
+			$this->checkPassword(
646
+				$model,
647
+				$db_row,
648
+				$model->alter_query_params_to_restrict_by_ID(
649
+					$model->get_index_primary_key_string(
650
+						$model->deduce_fields_n_values_from_cols_n_values($db_row)
651
+					)
652
+				),
653
+				$rest_request
654
+			);
655
+		} catch (RestPasswordRequiredException $e) {
656
+			if ($model->hasPassword()) {
657
+				// just remove protected fields
658
+				$has_protected_fields = true;
659
+				$entity_array         = Capabilities::filterOutPasswordProtectedFields(
660
+					$entity_array,
661
+					$model,
662
+					$this->getModelVersionInfo()
663
+				);
664
+			} else {
665
+				// that's a problem. None of this should be accessible if no password was provided
666
+				throw $e;
667
+			}
668
+		}
669
+
670
+		$entity_array['_calculated_fields'] =
671
+			$this->getEntityCalculations($model, $db_row, $rest_request, $has_protected_fields);
672
+		$entity_array                       = apply_filters(
673
+			'FHEE__Read__create_entity_from_wpdb_results__entity_before_including_requested_models',
674
+			$entity_array,
675
+			$model,
676
+			$rest_request->get_param('caps'),
677
+			$rest_request,
678
+			$this
679
+		);
680
+		// add an empty protected property for now. If it's still around after we remove everything the request didn't
681
+		// want, we'll populate it then. k?
682
+		$entity_array['_protected'] = [];
683
+		// remove any properties the request didn't want. This way _protected won't bother mentioning them
684
+		$entity_array = $this->includeOnlyRequestedProperties($model, $rest_request, $entity_array);
685
+		$entity_array =
686
+			$this->includeRequestedModels($model, $rest_request, $entity_array, $db_row, $has_protected_fields);
687
+		// if they still wanted the _protected property, add it.
688
+		if (isset($entity_array['_protected'])) {
689
+			$entity_array = $this->addProtectedProperty($model, $entity_array, $has_protected_fields);
690
+		}
691
+		$entity_array = apply_filters(
692
+			'FHEE__Read__create_entity_from_wpdb_results__entity_before_inaccessible_field_removal',
693
+			$entity_array,
694
+			$model,
695
+			$rest_request->get_param('caps'),
696
+			$rest_request,
697
+			$this
698
+		);
699
+		if (! $current_user_full_access_to_entity) {
700
+			$result_without_inaccessible_fields = Capabilities::filterOutInaccessibleEntityFields(
701
+				$entity_array,
702
+				$model,
703
+				$rest_request->get_param('caps'),
704
+				$this->getModelVersionInfo()
705
+			);
706
+		} else {
707
+			$result_without_inaccessible_fields = $entity_array;
708
+		}
709
+		$this->setDebugInfo(
710
+			'inaccessible fields',
711
+			array_keys(array_diff_key((array) $entity_array, (array) $result_without_inaccessible_fields))
712
+		);
713
+		return apply_filters(
714
+			'FHEE__Read__create_entity_from_wpdb_results__entity_return',
715
+			$result_without_inaccessible_fields,
716
+			$model,
717
+			$rest_request->get_param('caps')
718
+		);
719
+	}
720
+
721
+
722
+	/**
723
+	 * Returns an array describing which fields can be protected, and which actually were removed this request
724
+	 *
725
+	 * @param EEM_Base $model
726
+	 * @param array    $results_so_far
727
+	 * @param bool     $protected
728
+	 * @return array results
729
+	 * @throws EE_Error
730
+	 * @since 4.9.74.p
731
+	 */
732
+	protected function addProtectedProperty(EEM_Base $model, array $results_so_far, bool $protected): array
733
+	{
734
+		if (! $model->hasPassword() || ! $protected) {
735
+			return $results_so_far;
736
+		}
737
+		$password_field  = $model->getPasswordField();
738
+		$all_protected   = array_merge(
739
+			[$password_field->get_name()],
740
+			$password_field->protectedFields()
741
+		);
742
+		$fields_included = array_keys($results_so_far);
743
+		$fields_included = array_intersect(
744
+			$all_protected,
745
+			$fields_included
746
+		);
747
+		foreach ($fields_included as $field_name) {
748
+			$results_so_far['_protected'][] = $field_name;
749
+		}
750
+		return $results_so_far;
751
+	}
752
+
753
+
754
+	/**
755
+	 * Creates a REST entity array (JSON object we're going to return in the response, but
756
+	 * for now still a PHP array, but soon enough we'll call json_encode on it, don't worry),
757
+	 * from $wpdb->get_row( $sql, ARRAY_A)
758
+	 *
759
+	 * @param EEM_Base $model
760
+	 * @param array    $db_row
761
+	 * @return array entity mostly ready for converting to JSON and sending in the response
762
+	 * @throws EE_Error
763
+	 * @throws ReflectionException
764
+	 * @throws RestException
765
+	 */
766
+	protected function createBareEntityFromWpdbResults(EEM_Base $model, array $db_row): array
767
+	{
768
+		$result = $model->deduce_fields_n_values_from_cols_n_values($db_row);
769
+		$result = array_intersect_key(
770
+			$result,
771
+			$this->getModelVersionInfo()->fieldsOnModelInThisVersion($model)
772
+		);
773
+		// if this is a CPT, we need to set the global $post to it,
774
+		// otherwise shortcodes etc won't work properly while rendering it
775
+		if ($model instanceof EEM_CPT_Base) {
776
+			$do_chevy_shuffle = true;
777
+		} else {
778
+			$do_chevy_shuffle = false;
779
+		}
780
+		if ($do_chevy_shuffle) {
781
+			global $post;
782
+			$old_post = $post;
783
+			$post     = get_post($result[ $model->primary_key_name() ]);
784
+			if (! $post instanceof WP_Post) {
785
+				// well that's weird, because $result is what we JUST fetched from the database
786
+				throw new RestException(
787
+					'error_fetching_post_from_database_results',
788
+					esc_html__(
789
+						'An item was retrieved from the database but it\'s not a WP_Post like it should be.',
790
+						'event_espresso'
791
+					)
792
+				);
793
+			}
794
+			$model_object_classname          = 'EE_' . $model->get_this_model_name();
795
+			$post->{$model_object_classname} = EE_Registry::instance()->load_class(
796
+				$model_object_classname,
797
+				$result,
798
+				false,
799
+				false
800
+			);
801
+		}
802
+		foreach ($result as $field_name => $field_value) {
803
+			$field_obj = $model->field_settings_for($field_name);
804
+			if ($this->isSubclassOfOne($field_obj, $this->getModelVersionInfo()->fieldsIgnored())) {
805
+				unset($result[ $field_name ]);
806
+			} elseif (
807
+				$this->isSubclassOfOne(
808
+					$field_obj,
809
+					$this->getModelVersionInfo()->fieldsThatHaveRenderedFormat()
810
+				)
811
+			) {
812
+				$result[ $field_name ] = [
813
+					'raw'      => $this->prepareFieldObjValueForJson($field_obj, $field_value),
814
+					'rendered' => $this->prepareFieldObjValueForJson($field_obj, $field_value, 'pretty'),
815
+				];
816
+			} elseif (
817
+				$this->isSubclassOfOne(
818
+					$field_obj,
819
+					$this->getModelVersionInfo()->fieldsThatHavePrettyFormat()
820
+				)
821
+			) {
822
+				$result[ $field_name ] = [
823
+					'raw'    => $this->prepareFieldObjValueForJson($field_obj, $field_value),
824
+					'pretty' => $this->prepareFieldObjValueForJson($field_obj, $field_value, 'pretty'),
825
+				];
826
+			} elseif ($field_obj instanceof EE_Datetime_Field) {
827
+				$field_value = $field_obj->prepare_for_set_from_db($field_value);
828
+				// if the value is null, but we're not supposed to permit null, then set to the field's default
829
+				if (is_null($field_value)) {
830
+					$field_value = $field_obj->getDefaultDateTimeObj();
831
+				}
832
+				if (is_null($field_value)) {
833
+					$gmt_date = $local_date = ModelDataTranslator::prepareFieldValuesForJson(
834
+						$field_obj,
835
+						$field_value,
836
+						$this->getModelVersionInfo()->requestedVersion()
837
+					);
838
+				} else {
839
+					$timezone = $field_value->getTimezone();
840
+					EEH_DTT_Helper::setTimezone($field_value, new DateTimeZone('UTC'));
841
+					$gmt_date = ModelDataTranslator::prepareFieldValuesForJson(
842
+						$field_obj,
843
+						$field_value,
844
+						$this->getModelVersionInfo()->requestedVersion()
845
+					);
846
+					EEH_DTT_Helper::setTimezone($field_value, $timezone);
847
+					$local_date = ModelDataTranslator::prepareFieldValuesForJson(
848
+						$field_obj,
849
+						$field_value,
850
+						$this->getModelVersionInfo()->requestedVersion()
851
+					);
852
+				}
853
+				$result[ $field_name . '_gmt' ] = $gmt_date;
854
+				$result[ $field_name ]          = $local_date;
855
+			} else {
856
+				$result[ $field_name ] = $this->prepareFieldObjValueForJson($field_obj, $field_value);
857
+			}
858
+		}
859
+		if ($do_chevy_shuffle) {
860
+			$post = $old_post;
861
+		}
862
+		return $result;
863
+	}
864
+
865
+
866
+	/**
867
+	 * Takes a value all the way from the DB representation, to the model object's representation, to the
868
+	 * user-facing PHP representation, to the REST API representation. (Assumes you've already taken from the DB
869
+	 * representation using $field_obj->prepare_for_set_from_db())
870
+	 *
871
+	 * @param EE_Model_Field_Base $field_obj
872
+	 * @param mixed               $value  as it's stored on a model object
873
+	 * @param string              $format valid values are 'normal' (default), 'pretty', 'datetime_obj'
874
+	 * @return mixed
875
+	 * @throws RestException if $value contains a PHP object
876
+	 * @throws EE_Error
877
+	 */
878
+	protected function prepareFieldObjValueForJson(
879
+		EE_Model_Field_Base $field_obj,
880
+		$value,
881
+		string $format = 'normal'
882
+	) {
883
+		$value = $field_obj->prepare_for_set_from_db($value);
884
+		switch ($format) {
885
+			case 'pretty':
886
+				$value = $field_obj->prepare_for_pretty_echoing($value);
887
+				break;
888
+			case 'normal':
889
+			default:
890
+				$value = $field_obj->prepare_for_get($value);
891
+				break;
892
+		}
893
+		return ModelDataTranslator::prepareFieldValuesForJson(
894
+			$field_obj,
895
+			$value,
896
+			$this->getModelVersionInfo()->requestedVersion()
897
+		);
898
+	}
899
+
900
+
901
+	/**
902
+	 * Adds a few extra fields to the entity response
903
+	 *
904
+	 * @param EEM_Base $model
905
+	 * @param array    $db_row
906
+	 * @param array    $entity_array
907
+	 * @return array modified entity
908
+	 * @throws EE_Error
909
+	 * @throws EE_Error
910
+	 */
911
+	protected function addExtraFields(EEM_Base $model, array $db_row, array $entity_array): array
912
+	{
913
+		if ($model instanceof EEM_CPT_Base) {
914
+			$entity_array['link'] = get_permalink($db_row[ $model->get_primary_key_field()->get_qualified_column() ]);
915
+		}
916
+		return $entity_array;
917
+	}
918
+
919
+
920
+	/**
921
+	 * Gets links we want to add to the response
922
+	 *
923
+	 * @param EEM_Base        $model
924
+	 * @param array           $db_row
925
+	 * @param array           $entity_array
926
+	 * @return array the _links item in the entity
927
+	 * @throws EE_Error
928
+	 * @throws EE_Error
929
+	 * @global WP_REST_Server $wp_rest_server
930
+	 */
931
+	protected function getEntityLinks(EEM_Base $model, array $db_row, array $entity_array): array
932
+	{
933
+		// add basic links
934
+		$links = [];
935
+		if ($model->has_primary_key_field()) {
936
+			$links['self'] = [
937
+				[
938
+					'href' => $this->getVersionedLinkTo(
939
+						EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
940
+						. '/'
941
+						. $entity_array[ $model->primary_key_name() ]
942
+					),
943
+				],
944
+			];
945
+		}
946
+		$links['collection'] = [
947
+			[
948
+				'href' => $this->getVersionedLinkTo(
949
+					EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
950
+				),
951
+			],
952
+		];
953
+		// add links to related models
954
+		if ($model->has_primary_key_field()) {
955
+			foreach ($this->getModelVersionInfo()->relationSettings($model) as $relation_name => $relation_obj) {
956
+				$related_model_part                                                      =
957
+					Read::getRelatedEntityName($relation_name, $relation_obj);
958
+				$links[ EED_Core_Rest_Api::ee_api_link_namespace . $related_model_part ] = [
959
+					[
960
+						'href'   => $this->getVersionedLinkTo(
961
+							EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
962
+							. '/'
963
+							. $entity_array[ $model->primary_key_name() ]
964
+							. '/'
965
+							. $related_model_part
966
+						),
967
+						'single' => $relation_obj instanceof EE_Belongs_To_Relation,
968
+					],
969
+				];
970
+			}
971
+		}
972
+		return $links;
973
+	}
974
+
975
+
976
+	/**
977
+	 * Adds the included models indicated in the request to the entity provided
978
+	 *
979
+	 * @param EEM_Base        $model
980
+	 * @param WP_REST_Request $rest_request
981
+	 * @param array           $entity_array
982
+	 * @param array           $db_row
983
+	 * @param boolean         $included_items_protected if the original item is password protected, don't include any
984
+	 *                                                  related models.
985
+	 * @return array the modified entity
986
+	 * @throws EE_Error
987
+	 * @throws ReflectionException
988
+	 */
989
+	protected function includeRequestedModels(
990
+		EEM_Base $model,
991
+		WP_REST_Request $rest_request,
992
+		array $entity_array,
993
+		array $db_row = [],
994
+		bool $included_items_protected = false
995
+	): array {
996
+		// if $db_row not included, hope the entity array has what we need
997
+		if (! $db_row) {
998
+			$db_row = $entity_array;
999
+		}
1000
+		$relation_settings = $this->getModelVersionInfo()->relationSettings($model);
1001
+		foreach ($relation_settings as $relation_name => $relation_obj) {
1002
+			$related_fields_to_include   = $this->explodeAndGetItemsPrefixedWith(
1003
+				$rest_request->get_param('include'),
1004
+				$relation_name
1005
+			);
1006
+			$related_fields_to_calculate = $this->explodeAndGetItemsPrefixedWith(
1007
+				$rest_request->get_param('calculate'),
1008
+				$relation_name
1009
+			);
1010
+			// did they specify they wanted to include a related model, or
1011
+			// specific fields from a related model?
1012
+			// or did they specify to calculate a field from a related model?
1013
+			if ($related_fields_to_include || $related_fields_to_calculate) {
1014
+				// if so, we should include at least some part of the related model
1015
+				$pretend_related_request = new WP_REST_Request();
1016
+				$pretend_related_request->set_query_params(
1017
+					[
1018
+						'caps'      => $rest_request->get_param('caps'),
1019
+						'include'   => $related_fields_to_include,
1020
+						'calculate' => $related_fields_to_calculate,
1021
+						'password'  => $rest_request->get_param('password'),
1022
+					]
1023
+				);
1024
+				$pretend_related_request->add_header('no_rest_headers', true);
1025
+				$primary_model_query_params = $model->alter_query_params_to_restrict_by_ID(
1026
+					$model->get_index_primary_key_string(
1027
+						$model->deduce_fields_n_values_from_cols_n_values($db_row)
1028
+					)
1029
+				);
1030
+				if (! $included_items_protected) {
1031
+					try {
1032
+						$related_results = $this->getEntitiesFromRelationUsingModelQueryParams(
1033
+							$primary_model_query_params,
1034
+							$relation_obj,
1035
+							$pretend_related_request
1036
+						);
1037
+					} catch (RestException $e) {
1038
+						$related_results = new WP_Error('entity_relations_error', $e->getMessage());
1039
+					}
1040
+				} else {
1041
+					// they're protected, hide them.
1042
+					$related_results              = null;
1043
+					$entity_array['_protected'][] = Read::getRelatedEntityName($relation_name, $relation_obj);
1044
+				}
1045
+				if ($related_results instanceof WP_Error) {
1046
+					$related_results = $relation_obj instanceof EE_Belongs_To_Relation
1047
+						? null
1048
+						: [];
1049
+				}
1050
+				$entity_array[ Read::getRelatedEntityName($relation_name, $relation_obj) ] = $related_results;
1051
+			}
1052
+		}
1053
+		return $entity_array;
1054
+	}
1055
+
1056
+
1057
+	/**
1058
+	 * If the user has requested only specific properties (including meta properties like _links or _protected)
1059
+	 * remove everything else.
1060
+	 *
1061
+	 * @param EEM_Base        $model
1062
+	 * @param WP_REST_Request $rest_request
1063
+	 * @param                 $entity_array
1064
+	 * @return array
1065
+	 * @throws EE_Error
1066
+	 * @since 4.9.74.p
1067
+	 */
1068
+	protected function includeOnlyRequestedProperties(
1069
+		EEM_Base $model,
1070
+		WP_REST_Request $rest_request,
1071
+		$entity_array
1072
+	): array {
1073
+		$includes_for_this_model = $this->explodeAndGetItemsPrefixedWith($rest_request->get_param('include'), '');
1074
+		$includes_for_this_model = $this->removeModelNamesFromArray($includes_for_this_model);
1075
+		// if they passed in * or didn't specify any includes, return everything
1076
+		if (
1077
+			! in_array('*', $includes_for_this_model)
1078
+			&& ! empty($includes_for_this_model)
1079
+		) {
1080
+			if ($model->has_primary_key_field()) {
1081
+				// always include the primary key. ya just gotta know that at least
1082
+				$includes_for_this_model[] = $model->primary_key_name();
1083
+			}
1084
+			if ($this->explodeAndGetItemsPrefixedWith($rest_request->get_param('calculate'), '')) {
1085
+				$includes_for_this_model[] = '_calculated_fields';
1086
+			}
1087
+			$entity_array = array_intersect_key($entity_array, array_flip($includes_for_this_model));
1088
+		}
1089
+		return $entity_array;
1090
+	}
1091
+
1092
+
1093
+	/**
1094
+	 * Returns a new array with all the names of models removed. Eg
1095
+	 * array( 'Event', 'Datetime.*', 'foobar' ) would become array( 'Datetime.*', 'foobar' )
1096
+	 *
1097
+	 * @param array $model_names
1098
+	 * @return array
1099
+	 */
1100
+	private function removeModelNamesFromArray(array $model_names): array
1101
+	{
1102
+		return array_diff($model_names, array_keys(EE_Registry::instance()->non_abstract_db_models));
1103
+	}
1104
+
1105
+
1106
+	/**
1107
+	 * Gets the calculated fields for the response
1108
+	 *
1109
+	 * @param EEM_Base        $model
1110
+	 * @param array           $wpdb_row
1111
+	 * @param WP_REST_Request $rest_request
1112
+	 * @param boolean         $row_is_protected whether this row is password protected or not
1113
+	 * @return stdClass the _calculations item in the entity
1114
+	 * @throws RestException if a default value has a PHP object, which should never do (and if we
1115
+	 * @throws EE_Error
1116
+	 *                                          did, let's know about it ASAP, so let the exception bubble up)
1117
+	 */
1118
+	protected function getEntityCalculations(
1119
+		EEM_Base $model,
1120
+		array $wpdb_row,
1121
+		WP_REST_Request $rest_request,
1122
+		bool $row_is_protected = false
1123
+	): stdClass {
1124
+		$calculated_fields = $this->explodeAndGetItemsPrefixedWith(
1125
+			$rest_request->get_param('calculate'),
1126
+			''
1127
+		);
1128
+		// note: setting calculate=* doesn't do anything
1129
+		$calculated_fields_to_return = new stdClass();
1130
+		$protected_fields            = [];
1131
+		foreach ($calculated_fields as $field_to_calculate) {
1132
+			try {
1133
+				// it's password protected, so they shouldn't be able to read this. Remove the value
1134
+				$schema = $this->fields_calculator->getJsonSchemaForModel($model);
1135
+				if (
1136
+					$row_is_protected
1137
+					&& isset($schema['properties'][ $field_to_calculate ]['protected'])
1138
+					&& $schema['properties'][ $field_to_calculate ]['protected']
1139
+				) {
1140
+					$calculated_value   = null;
1141
+					$protected_fields[] = $field_to_calculate;
1142
+					if ($schema['properties'][ $field_to_calculate ]['type']) {
1143
+						switch ($schema['properties'][ $field_to_calculate ]['type']) {
1144
+							case 'boolean':
1145
+								$calculated_value = false;
1146
+								break;
1147
+							case 'integer':
1148
+								$calculated_value = 0;
1149
+								break;
1150
+							case 'string':
1151
+								$calculated_value = '';
1152
+								break;
1153
+							case 'array':
1154
+								$calculated_value = [];
1155
+								break;
1156
+							case 'object':
1157
+								$calculated_value = new stdClass();
1158
+								break;
1159
+						}
1160
+					}
1161
+				} else {
1162
+					$calculated_value = ModelDataTranslator::prepareFieldValueForJson(
1163
+						null,
1164
+						$this->fields_calculator->retrieveCalculatedFieldValue(
1165
+							$model,
1166
+							$field_to_calculate,
1167
+							$wpdb_row,
1168
+							$rest_request,
1169
+							$this
1170
+						),
1171
+						$this->getModelVersionInfo()->requestedVersion()
1172
+					);
1173
+				}
1174
+				$calculated_fields_to_return->{$field_to_calculate} = $calculated_value;
1175
+			} catch (RestException $e) {
1176
+				// if we don't have permission to read it, just leave it out. but let devs know about the problem
1177
+				$this->setResponseHeader(
1178
+					'Notices-Field-Calculation-Errors['
1179
+					. $e->getStringCode()
1180
+					. ']['
1181
+					. $model->get_this_model_name()
1182
+					. ']['
1183
+					. $field_to_calculate
1184
+					. ']',
1185
+					$e->getMessage()
1186
+				);
1187
+			}
1188
+		}
1189
+		$calculated_fields_to_return->_protected = $protected_fields;
1190
+		return $calculated_fields_to_return;
1191
+	}
1192
+
1193
+
1194
+	/**
1195
+	 * Gets the full URL to the resource, taking the requested version into account
1196
+	 *
1197
+	 * @param string $link_part_after_version_and_slash eg "events/10/datetimes"
1198
+	 * @return string url eg "http://mysite.com/wp-json/ee/v4.6/events/10/datetimes"
1199
+	 * @throws EE_Error
1200
+	 * @throws EE_Error
1201
+	 */
1202
+	public function getVersionedLinkTo(string $link_part_after_version_and_slash): string
1203
+	{
1204
+		return rest_url(
1205
+			EED_Core_Rest_Api::get_versioned_route_to(
1206
+				$link_part_after_version_and_slash,
1207
+				$this->getModelVersionInfo()->requestedVersion()
1208
+			)
1209
+		);
1210
+	}
1211
+
1212
+
1213
+	/**
1214
+	 * Gets the correct lowercase name for the relation in the API according
1215
+	 * to the relation's type
1216
+	 *
1217
+	 * @param string                 $relation_name
1218
+	 * @param EE_Model_Relation_Base $relation_obj
1219
+	 * @return string
1220
+	 */
1221
+	public static function getRelatedEntityName(string $relation_name, EE_Model_Relation_Base $relation_obj): string
1222
+	{
1223
+		if ($relation_obj instanceof EE_Belongs_To_Relation) {
1224
+			return strtolower($relation_name);
1225
+		} else {
1226
+			return EEH_Inflector::pluralize_and_lower($relation_name);
1227
+		}
1228
+	}
1229
+
1230
+
1231
+	/**
1232
+	 * Gets the one model object with the specified id for the specified model
1233
+	 *
1234
+	 * @param EEM_Base        $model
1235
+	 * @param WP_REST_Request $request
1236
+	 * @return array
1237
+	 * @throws EE_Error
1238
+	 * @throws EE_Error
1239
+	 * @throws ReflectionException
1240
+	 */
1241
+	public function getEntityFromModel(EEM_Base $model, WP_REST_Request $request): array
1242
+	{
1243
+		$context = $this->validateContext($request->get_param('caps'));
1244
+		return $this->getOneOrReportPermissionError($model, $request, $context);
1245
+	}
1246
+
1247
+
1248
+	/**
1249
+	 * If a context is provided which isn't valid, maybe it was added in a future
1250
+	 * version so just treat it as a default read
1251
+	 *
1252
+	 * @param string $context
1253
+	 * @return string array key of EEM_Base::cap_contexts_to_cap_action_map()
1254
+	 */
1255
+	public function validateContext(string $context): string
1256
+	{
1257
+		if (! $context) {
1258
+			$context = EEM_Base::caps_read;
1259
+		}
1260
+		$valid_contexts = EEM_Base::valid_cap_contexts();
1261
+		if (in_array($context, $valid_contexts)) {
1262
+			return $context;
1263
+		} else {
1264
+			return EEM_Base::caps_read;
1265
+		}
1266
+	}
1267
+
1268
+
1269
+	/**
1270
+	 * Verifies the passed in value is an allowable default where conditions value.
1271
+	 *
1272
+	 * @param $default_query_params
1273
+	 * @return string
1274
+	 */
1275
+	public function validateDefaultQueryParams($default_query_params): string
1276
+	{
1277
+		$valid_default_where_conditions_for_api_calls = [
1278
+			EEM_Base::default_where_conditions_all,
1279
+			EEM_Base::default_where_conditions_minimum_all,
1280
+			EEM_Base::default_where_conditions_minimum_others,
1281
+		];
1282
+		if (! $default_query_params) {
1283
+			$default_query_params = EEM_Base::default_where_conditions_all;
1284
+		}
1285
+		if (
1286
+			in_array(
1287
+				$default_query_params,
1288
+				$valid_default_where_conditions_for_api_calls,
1289
+				true
1290
+			)
1291
+		) {
1292
+			return $default_query_params;
1293
+		}
1294
+		return EEM_Base::default_where_conditions_all;
1295
+	}
1296
+
1297
+
1298
+	/**
1299
+	 * Translates API filter get parameter into model query params @param EEM_Base $model
1300
+	 *
1301
+	 * @param array $query_params
1302
+	 * @return array model query params (@see
1303
+	 *               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions)
1304
+	 *               or FALSE to indicate that absolutely no results should be returned
1305
+	 * @throws EE_Error
1306
+	 * @throws RestException
1307
+	 * @see
1308
+	 *               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions.
1309
+	 *               Note: right now the query parameter keys for fields (and related fields) can be left as-is, but
1310
+	 *               it's quite possible this will change someday. Also, this method's contents might be candidate for
1311
+	 *               moving to Model_Data_Translator
1312
+	 *
1313
+	 */
1314
+	public function createModelQueryParams(EEM_Base $model, array $query_params): array
1315
+	{
1316
+		$model_query_params = [];
1317
+		if (isset($query_params['where'])) {
1318
+			$model_query_params[0] = ModelDataTranslator::prepareConditionsQueryParamsForModels(
1319
+				$query_params['where'],
1320
+				$model,
1321
+				$this->getModelVersionInfo()->requestedVersion()
1322
+			);
1323
+		}
1324
+		if (isset($query_params['order_by'])) {
1325
+			$order_by = $query_params['order_by'];
1326
+		} elseif (isset($query_params['orderby'])) {
1327
+			$order_by = $query_params['orderby'];
1328
+		} else {
1329
+			$order_by = null;
1330
+		}
1331
+		if ($order_by !== null) {
1332
+			if (is_array($order_by)) {
1333
+				$order_by = ModelDataTranslator::prepareFieldNamesInArrayKeysFromJson($order_by);
1334
+			} else {
1335
+				// it's a single item
1336
+				$order_by = ModelDataTranslator::prepareFieldNameFromJson($order_by);
1337
+			}
1338
+			$model_query_params['order_by'] = $order_by;
1339
+		}
1340
+		if (isset($query_params['group_by'])) {
1341
+			$group_by = $query_params['group_by'];
1342
+		} elseif (isset($query_params['groupby'])) {
1343
+			$group_by = $query_params['groupby'];
1344
+		} else {
1345
+			$group_by = array_keys($model->get_combined_primary_key_fields());
1346
+		}
1347
+		// make sure they're all real names
1348
+		if (is_array($group_by)) {
1349
+			$group_by = ModelDataTranslator::prepareFieldNamesFromJson($group_by);
1350
+		}
1351
+		if ($group_by !== null) {
1352
+			$model_query_params['group_by'] = $group_by;
1353
+		}
1354
+		if (isset($query_params['having'])) {
1355
+			$model_query_params['having'] = ModelDataTranslator::prepareConditionsQueryParamsForModels(
1356
+				$query_params['having'],
1357
+				$model,
1358
+				$this->getModelVersionInfo()->requestedVersion()
1359
+			);
1360
+		}
1361
+		if (isset($query_params['order'])) {
1362
+			$model_query_params['order'] = $query_params['order'];
1363
+		}
1364
+		if (isset($query_params['mine'])) {
1365
+			$model_query_params = $model->alter_query_params_to_only_include_mine($model_query_params);
1366
+		}
1367
+		if (isset($query_params['limit'])) {
1368
+			// limit should be either a string like '23' or '23,43', or an array with two items in it
1369
+			if (! is_array($query_params['limit'])) {
1370
+				$limit_array = explode(',', (string) $query_params['limit']);
1371
+			} else {
1372
+				$limit_array = $query_params['limit'];
1373
+			}
1374
+			$sanitized_limit = [];
1375
+			foreach ($limit_array as $limit_part) {
1376
+				if ($this->debug_mode && (! is_numeric($limit_part) || count($sanitized_limit) > 2)) {
1377
+					throw new EE_Error(
1378
+						sprintf(
1379
+							esc_html__(
1380
+							// @codingStandardsIgnoreStart
1381
+								'An invalid limit filter was provided. It was: %s. If the EE4 JSON REST API weren\'t in debug mode, this message would not appear.',
1382
+								// @codingStandardsIgnoreEnd
1383
+								'event_espresso'
1384
+							),
1385
+							wp_json_encode($query_params['limit'])
1386
+						)
1387
+					);
1388
+				}
1389
+				$sanitized_limit[] = (int) $limit_part;
1390
+			}
1391
+			$model_query_params['limit'] = implode(',', $sanitized_limit);
1392
+		} else {
1393
+			$model_query_params['limit'] = EED_Core_Rest_Api::get_default_query_limit();
1394
+		}
1395
+		if (isset($query_params['caps'])) {
1396
+			$model_query_params['caps'] = $this->validateContext($query_params['caps']);
1397
+		} else {
1398
+			$model_query_params['caps'] = EEM_Base::caps_read;
1399
+		}
1400
+		if (isset($query_params['default_where_conditions'])) {
1401
+			$model_query_params['default_where_conditions'] = $this->validateDefaultQueryParams(
1402
+				$query_params['default_where_conditions']
1403
+			);
1404
+		}
1405
+		// if this is a model protected by a password on another model, exclude the password protected
1406
+		// entities by default. But if they passed in a password, try to show them all. If the password is wrong,
1407
+		// though, they'll get an error (see Read::createEntityFromWpdbResult() which calls Read::checkPassword)
1408
+		if (
1409
+			! $model->hasPassword()
1410
+			&& $model->restrictedByRelatedModelPassword()
1411
+			&& $model_query_params['caps'] === EEM_Base::caps_read
1412
+		) {
1413
+			if (empty($query_params['password'])) {
1414
+				$model_query_params['exclude_protected'] = true;
1415
+			}
1416
+		}
1417
+
1418
+		return apply_filters('FHEE__Read__create_model_query_params', $model_query_params, $query_params, $model);
1419
+	}
1420
+
1421
+
1422
+	/**
1423
+	 * Changes the REST-style query params for use in the models
1424
+	 *
1425
+	 * @param EEM_Base $model
1426
+	 * @param array    $query_params sub-array from @see EEM_Base::get_all()
1427
+	 * @return array
1428
+	 * @deprecated
1429
+	 */
1430
+	public function prepareRestQueryParamsKeyForModels(EEM_Base $model, array $query_params): array
1431
+	{
1432
+		$model_ready_query_params = [];
1433
+		foreach ($query_params as $key => $value) {
1434
+			$model_ready_query_params[ $key ] = is_array($value)
1435
+				? $this->prepareRestQueryParamsKeyForModels($model, $value)
1436
+				: $value;
1437
+		}
1438
+		return $model_ready_query_params;
1439
+	}
1440
+
1441
+
1442
+	/**
1443
+	 * @param EEM_Base $model
1444
+	 * @param array    $query_params
1445
+	 * @return array
1446
+	 * @deprecated instead use ModelDataTranslator::prepareFieldValuesFromJson()
1447
+	 */
1448
+	public function prepareRestQueryParamsValuesForModels(EEM_Base $model, array $query_params): array
1449
+	{
1450
+		$model_ready_query_params = [];
1451
+		foreach ($query_params as $key => $value) {
1452
+			if (is_array($value)) {
1453
+				$model_ready_query_params[ $key ] = $this->prepareRestQueryParamsValuesForModels($model, $value);
1454
+			} else {
1455
+				$model_ready_query_params[ $key ] = $value;
1456
+			}
1457
+		}
1458
+		return $model_ready_query_params;
1459
+	}
1460
+
1461
+
1462
+	/**
1463
+	 * Explodes the string on commas, and only returns items with $prefix followed by a period.
1464
+	 * If no prefix is specified, returns items with no period.
1465
+	 *
1466
+	 * @param string|array $string_to_explode eg "jibba,jabba, blah, blah, blah" or array('jibba', 'jabba' )
1467
+	 * @param string       $prefix            "Event" or "foobar"
1468
+	 * @return array $string_to_exploded exploded on COMMAS, and if a prefix was specified
1469
+	 *                                        we only return strings starting with that and a period; if no prefix was
1470
+	 *                                        specified we return all items containing NO periods
1471
+	 */
1472
+	public function explodeAndGetItemsPrefixedWith($string_to_explode, string $prefix): array
1473
+	{
1474
+		if (is_string($string_to_explode)) {
1475
+			$exploded_contents = explode(',', $string_to_explode);
1476
+		} elseif (is_array($string_to_explode)) {
1477
+			$exploded_contents = $string_to_explode;
1478
+		} else {
1479
+			$exploded_contents = [];
1480
+		}
1481
+		// if the string was empty, we want an empty array
1482
+		$exploded_contents    = array_filter($exploded_contents);
1483
+		$contents_with_prefix = [];
1484
+		foreach ($exploded_contents as $item) {
1485
+			$item = trim($item);
1486
+			// if no prefix was provided, so we look for items with no "." in them
1487
+			if (! $prefix) {
1488
+				// does this item have a period?
1489
+				if (strpos($item, '.') === false) {
1490
+					// if not, then its what we're looking for
1491
+					$contents_with_prefix[] = $item;
1492
+				}
1493
+			} elseif (strpos($item, $prefix . '.') === 0) {
1494
+				// this item has the prefix and a period, grab it
1495
+				$contents_with_prefix[] = substr(
1496
+					$item,
1497
+					strpos($item, $prefix . '.') + strlen($prefix . '.')
1498
+				);
1499
+			} elseif ($item === $prefix) {
1500
+				// this item is JUST the prefix
1501
+				// so let's grab everything after, which is a blank string
1502
+				$contents_with_prefix[] = '';
1503
+			}
1504
+		}
1505
+		return $contents_with_prefix;
1506
+	}
1507
+
1508
+
1509
+	/**
1510
+	 * @param array|string $include_string @see Read:handle_request_get_all
1511
+	 * @param string|null  $model_name
1512
+	 * @return array of fields for this model. If $model_name is provided, then
1513
+	 *                                     the fields for that model, with the model's name removed from each.
1514
+	 *                                     If $include_string was blank or '*' returns an empty array
1515
+	 * @throws EE_Error
1516
+	 * @throws EE_Error
1517
+	 * @deprecated since 4.8.36.rc.001 You should instead use Read::explode_and_get_items_prefixed_with.
1518
+	 *                                     Deprecated because its return values were really quite confusing- sometimes
1519
+	 *                                     it returned an empty array (when the include string was blank or '*') or
1520
+	 *                                     sometimes it returned array('*') (when you provided a model and a model of
1521
+	 *                                     that kind was found). Parses the $include_string so we fetch all the field
1522
+	 *                                     names relating to THIS model
1523
+	 *                                     (ie have NO period in them), or for the provided model (ie start with the
1524
+	 *                                     model name and then a period).
1525
+	 */
1526
+	public function extractIncludesForThisModel($include_string, string $model_name = ''): array
1527
+	{
1528
+		if (is_array($include_string)) {
1529
+			$include_string = implode(',', $include_string);
1530
+		}
1531
+		if ($include_string === '*' || $include_string === '') {
1532
+			return [];
1533
+		}
1534
+		$includes                    = explode(',', $include_string);
1535
+		$extracted_fields_to_include = [];
1536
+		if ($model_name) {
1537
+			foreach ($includes as $field_to_include) {
1538
+				$field_to_include = trim($field_to_include);
1539
+				if (strpos($field_to_include, $model_name . '.') === 0) {
1540
+					// found the model name at the exact start
1541
+					$field_sans_model_name         = str_replace($model_name . '.', '', $field_to_include);
1542
+					$extracted_fields_to_include[] = $field_sans_model_name;
1543
+				} elseif ($field_to_include == $model_name) {
1544
+					$extracted_fields_to_include[] = '*';
1545
+				}
1546
+			}
1547
+		} else {
1548
+			// look for ones with no period
1549
+			foreach ($includes as $field_to_include) {
1550
+				$field_to_include = trim($field_to_include);
1551
+				if (
1552
+					strpos($field_to_include, '.') === false
1553
+					&& ! $this->getModelVersionInfo()->isModelNameInThisVersion($field_to_include)
1554
+				) {
1555
+					$extracted_fields_to_include[] = $field_to_include;
1556
+				}
1557
+			}
1558
+		}
1559
+		return $extracted_fields_to_include;
1560
+	}
1561
+
1562
+
1563
+	/**
1564
+	 * Gets the single item using the model according to the request in the context given, otherwise
1565
+	 * returns that it's inaccessible to the current user
1566
+	 *
1567
+	 * @param EEM_Base        $model
1568
+	 * @param WP_REST_Request $request
1569
+	 * @param null            $context
1570
+	 * @return array
1571
+	 * @throws EE_Error
1572
+	 * @throws ReflectionException
1573
+	 */
1574
+	public function getOneOrReportPermissionError(EEM_Base $model, WP_REST_Request $request, $context = null): array
1575
+	{
1576
+		$query_params = [[$model->primary_key_name() => $request->get_param('id')], 'limit' => 1];
1577
+		if ($model instanceof EEM_Soft_Delete_Base) {
1578
+			$query_params = $model->alter_query_params_so_deleted_and_undeleted_items_included($query_params);
1579
+		}
1580
+		$restricted_query_params         = $query_params;
1581
+		$restricted_query_params['caps'] = $context;
1582
+		$this->setDebugInfo('model query params', $restricted_query_params);
1583
+		$model_rows = $model->get_all_wpdb_results($restricted_query_params);
1584
+		if (! empty($model_rows)) {
1585
+			return $this->createEntityFromWpdbResult(
1586
+				$model,
1587
+				reset($model_rows),
1588
+				$request
1589
+			);
1590
+		} else {
1591
+			// ok let's test to see if we WOULD have found it, had we not had restrictions from missing capabilities
1592
+			$lowercase_model_name = strtolower($model->get_this_model_name());
1593
+			if ($model->exists($query_params)) {
1594
+				// you got shafted- it existed but we didn't want to tell you!
1595
+				throw new RestException(
1596
+					'rest_user_cannot_' . $context,
1597
+					sprintf(
1598
+						esc_html__('Sorry, you cannot %1$s this %2$s. Missing permissions are: %3$s', 'event_espresso'),
1599
+						$context,
1600
+						$lowercase_model_name,
1601
+						Capabilities::getMissingPermissionsString(
1602
+							$model,
1603
+							$context
1604
+						)
1605
+					),
1606
+					['status' => 403]
1607
+				);
1608
+			} else {
1609
+				// it's not you. It just doesn't exist
1610
+				throw new RestException(
1611
+					sprintf('rest_%s_invalid_id', $lowercase_model_name),
1612
+					sprintf(esc_html__('Invalid %s ID.', 'event_espresso'), $lowercase_model_name),
1613
+					['status' => 404]
1614
+				);
1615
+			}
1616
+		}
1617
+	}
1618
+
1619
+
1620
+	/**
1621
+	 * Checks that if this content requires a password to be read, that it's been provided and is correct.
1622
+	 *
1623
+	 * @param EEM_Base        $model
1624
+	 * @param array           $model_row
1625
+	 * @param array           $query_params Adds 'default_where_conditions' => 'minimum'
1626
+	 *                                      to ensure we don't confuse trashed with password protected.
1627
+	 * @param WP_REST_Request $request
1628
+	 * @throws EE_Error
1629
+	 * @throws InvalidArgumentException
1630
+	 * @throws InvalidDataTypeException
1631
+	 * @throws InvalidInterfaceException
1632
+	 * @throws RestPasswordRequiredException
1633
+	 * @throws RestPasswordIncorrectException
1634
+	 * @throws ModelConfigurationException
1635
+	 * @throws ReflectionException
1636
+	 * @since 4.9.74.p
1637
+	 */
1638
+	protected function checkPassword(EEM_Base $model, array $model_row, array $query_params, WP_REST_Request $request)
1639
+	{
1640
+		$query_params['default_where_conditions'] = 'minimum';
1641
+		// stuff is only "protected" for front-end requests. Elsewhere, you either get full permission to access the object
1642
+		// or you don't.
1643
+		$request_caps = $request->get_param('caps');
1644
+		if (isset($request_caps) && $request_caps !== EEM_Base::caps_read) {
1645
+			return;
1646
+		}
1647
+		// if this entity requires a password, they better give it and it better be right!
1648
+		if (
1649
+			$model->hasPassword()
1650
+			&& $model_row[ $model->getPasswordField()->get_qualified_column() ] !== ''
1651
+		) {
1652
+			if (empty($request['password'])) {
1653
+				throw new RestPasswordRequiredException();
1654
+			}
1655
+			if (
1656
+				! hash_equals(
1657
+					$model_row[ $model->getPasswordField()->get_qualified_column() ],
1658
+					$request['password']
1659
+				)
1660
+			) {
1661
+				throw new RestPasswordIncorrectException();
1662
+			}
1663
+		} elseif (
1664
+			// wait! maybe this content is password protected
1665
+			$model->restrictedByRelatedModelPassword()
1666
+			&& $request->get_param('caps') === EEM_Base::caps_read
1667
+		) {
1668
+			$password_supplied = $request->get_param('password');
1669
+			if (empty($password_supplied)) {
1670
+				$query_params['exclude_protected'] = true;
1671
+				if (! $model->exists($query_params)) {
1672
+					throw new RestPasswordRequiredException();
1673
+				}
1674
+			} else {
1675
+				$query_params[0][ $model->modelChainAndPassword() ] = $password_supplied;
1676
+				if (! $model->exists($query_params)) {
1677
+					throw new RestPasswordIncorrectException();
1678
+				}
1679
+			}
1680
+		}
1681
+	}
1682
+
1683
+
1684
+	private static function endpointParsingError(string $model_name): WP_Error
1685
+	{
1686
+		return new WP_Error(
1687
+			'endpoint_parsing_error',
1688
+			sprintf(
1689
+				esc_html__(
1690
+					'There is no model for endpoint %s. Please contact event espresso support',
1691
+					'event_espresso'
1692
+				),
1693
+				$model_name
1694
+			)
1695
+		);
1696
+	}
1697 1697
 }
Please login to merge, or discard this patch.
Spacing   +63 added lines, -63 removed lines patch added patch discarded remove patch
@@ -100,7 +100,7 @@  discard block
 block discarded – undo
100 100
     ): WP_REST_Response {
101 101
         $controller = Read::getModelReadController($version);
102 102
         try {
103
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
103
+            if ( ! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
104 104
                 return $controller->sendResponse(Read::endpointParsingError($model_name));
105 105
             }
106 106
             return $controller->sendResponse(
@@ -129,7 +129,7 @@  discard block
 block discarded – undo
129 129
     {
130 130
         $controller = Read::getModelReadController($version);
131 131
         try {
132
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
132
+            if ( ! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
133 133
                 return [];
134 134
             }
135 135
             // get the model for this version
@@ -193,11 +193,11 @@  discard block
 block discarded – undo
193 193
      */
194 194
     protected function translateDefaultsForRestResponse($field_name, EE_Model_Field_Base $field, array $schema): array
195 195
     {
196
-        if (isset($schema['properties'][ $field_name ]['default'])) {
197
-            if (is_array($schema['properties'][ $field_name ]['default'])) {
198
-                foreach ($schema['properties'][ $field_name ]['default'] as $default_key => $default_value) {
196
+        if (isset($schema['properties'][$field_name]['default'])) {
197
+            if (is_array($schema['properties'][$field_name]['default'])) {
198
+                foreach ($schema['properties'][$field_name]['default'] as $default_key => $default_value) {
199 199
                     if ($default_key === 'raw') {
200
-                        $schema['properties'][ $field_name ]['default'][ $default_key ] =
200
+                        $schema['properties'][$field_name]['default'][$default_key] =
201 201
                             ModelDataTranslator::prepareFieldValueForJson(
202 202
                                 $field,
203 203
                                 $default_value,
@@ -206,9 +206,9 @@  discard block
 block discarded – undo
206 206
                     }
207 207
                 }
208 208
             } else {
209
-                $schema['properties'][ $field_name ]['default'] = ModelDataTranslator::prepareFieldValueForJson(
209
+                $schema['properties'][$field_name]['default'] = ModelDataTranslator::prepareFieldValueForJson(
210 210
                     $field,
211
-                    $schema['properties'][ $field_name ]['default'],
211
+                    $schema['properties'][$field_name]['default'],
212 212
                     $this->getModelVersionInfo()->requestedVersion()
213 213
                 );
214 214
             }
@@ -230,9 +230,9 @@  discard block
 block discarded – undo
230 230
     protected function maybeAddExtraFieldsToSchema($field_name, EE_Model_Field_Base $field, array $schema): array
231 231
     {
232 232
         if ($field instanceof EE_Datetime_Field) {
233
-            $schema['properties'][ $field_name . '_gmt' ] = $field->getSchema();
233
+            $schema['properties'][$field_name.'_gmt'] = $field->getSchema();
234 234
             // modify the description
235
-            $schema['properties'][ $field_name . '_gmt' ]['description'] = sprintf(
235
+            $schema['properties'][$field_name.'_gmt']['description'] = sprintf(
236 236
                 esc_html__('%s - the value for this field is in GMT.', 'event_espresso'),
237 237
                 wp_specialchars_decode($field->get_nicename(), ENT_QUOTES)
238 238
             );
@@ -282,7 +282,7 @@  discard block
 block discarded – undo
282 282
     ): WP_REST_Response {
283 283
         $controller = Read::getModelReadController($version);
284 284
         try {
285
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
285
+            if ( ! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
286 286
                 return $controller->sendResponse(Read::endpointParsingError($model_name));
287 287
             }
288 288
             return $controller->sendResponse(
@@ -349,7 +349,7 @@  discard block
 block discarded – undo
349 349
     public function getEntitiesFromModel(EEM_Base $model, WP_REST_Request $request): array
350 350
     {
351 351
         $query_params = $this->createModelQueryParams($model, $request->get_params());
352
-        if (! Capabilities::currentUserHasPartialAccessTo($model, $query_params['caps'])) {
352
+        if ( ! Capabilities::currentUserHasPartialAccessTo($model, $query_params['caps'])) {
353 353
             $model_name_plural = EEH_Inflector::pluralize_and_lower($model->get_this_model_name());
354 354
             throw new RestException(
355 355
                 sprintf('rest_%s_cannot_list', $model_name_plural),
@@ -361,7 +361,7 @@  discard block
 block discarded – undo
361 361
                 ['status' => 403]
362 362
             );
363 363
         }
364
-        if (! $request->get_header('no_rest_headers')) {
364
+        if ( ! $request->get_header('no_rest_headers')) {
365 365
             $this->setHeadersFromQueryParams($model, $query_params);
366 366
         }
367 367
         /** @type array $results */
@@ -405,7 +405,7 @@  discard block
 block discarded – undo
405 405
         $context       = $this->validateContext($request->get_param('caps'));
406 406
         $model         = $relation->get_this_model();
407 407
         $related_model = $relation->get_other_model();
408
-        if (! isset($primary_model_query_params[0])) {
408
+        if ( ! isset($primary_model_query_params[0])) {
409 409
             $primary_model_query_params[0] = [];
410 410
         }
411 411
         // check if they can access the 1st model object
@@ -469,13 +469,13 @@  discard block
 block discarded – undo
469 469
         );
470 470
         $query_params = $this->createModelQueryParams($relation->get_other_model(), $request->get_params());
471 471
         foreach ($primary_model_query_params[0] as $where_condition_key => $where_condition_value) {
472
-            $query_params[0][ $relation->get_this_model()->get_this_model_name()
472
+            $query_params[0][$relation->get_this_model()->get_this_model_name()
473 473
                               . '.'
474
-                              . $where_condition_key ] = $where_condition_value;
474
+                              . $where_condition_key] = $where_condition_value;
475 475
         }
476 476
         $query_params['default_where_conditions'] = 'none';
477 477
         $query_params['caps']                     = $context;
478
-        if (! $request->get_header('no_rest_headers')) {
478
+        if ( ! $request->get_header('no_rest_headers')) {
479 479
             $this->setHeadersFromQueryParams($relation->get_other_model(), $query_params);
480 480
         }
481 481
         /** @type array $results */
@@ -495,7 +495,7 @@  discard block
 block discarded – undo
495 495
                     $result,
496 496
                     $request
497 497
                 );
498
-                $joined_result     = array_merge($join_model_result, $nice_result);
498
+                $joined_result = array_merge($join_model_result, $nice_result);
499 499
                 // but keep the meta stuff from the main model
500 500
                 if (isset($nice_result['meta'])) {
501 501
                     $joined_result['meta'] = $nice_result['meta'];
@@ -530,7 +530,7 @@  discard block
 block discarded – undo
530 530
         EE_Model_Relation_Base $relation,
531 531
         WP_REST_Request $request
532 532
     ): array {
533
-        if (! $relation->get_this_model()->has_primary_key_field()) {
533
+        if ( ! $relation->get_this_model()->has_primary_key_field()) {
534 534
             throw new EE_Error(
535 535
                 sprintf(
536 536
                     esc_html__(
@@ -577,7 +577,7 @@  discard block
 block discarded – undo
577 577
             Capabilities::getMissingPermissionsString($model, $query_params['caps'])
578 578
         );
579 579
         // normally the limit to a 2-part array, where the 2nd item is the limit
580
-        if (! isset($query_params['limit'])) {
580
+        if ( ! isset($query_params['limit'])) {
581 581
             $query_params['limit'] = EED_Core_Rest_Api::get_default_query_limit();
582 582
         }
583 583
         if (is_array($query_params['limit'])) {
@@ -619,7 +619,7 @@  discard block
 block discarded – undo
619 619
         ?WP_REST_Request $rest_request,
620 620
         string $deprecated = null
621 621
     ): array {
622
-        if (! $rest_request instanceof WP_REST_Request) {
622
+        if ( ! $rest_request instanceof WP_REST_Request) {
623 623
             // ok so this was called in the old style, where the 3rd arg was
624 624
             // $include, and the 4th arg was $context
625 625
             // now setup the request just to avoid fatal errors, although we won't be able
@@ -696,7 +696,7 @@  discard block
 block discarded – undo
696 696
             $rest_request,
697 697
             $this
698 698
         );
699
-        if (! $current_user_full_access_to_entity) {
699
+        if ( ! $current_user_full_access_to_entity) {
700 700
             $result_without_inaccessible_fields = Capabilities::filterOutInaccessibleEntityFields(
701 701
                 $entity_array,
702 702
                 $model,
@@ -731,7 +731,7 @@  discard block
 block discarded – undo
731 731
      */
732 732
     protected function addProtectedProperty(EEM_Base $model, array $results_so_far, bool $protected): array
733 733
     {
734
-        if (! $model->hasPassword() || ! $protected) {
734
+        if ( ! $model->hasPassword() || ! $protected) {
735 735
             return $results_so_far;
736 736
         }
737 737
         $password_field  = $model->getPasswordField();
@@ -780,8 +780,8 @@  discard block
 block discarded – undo
780 780
         if ($do_chevy_shuffle) {
781 781
             global $post;
782 782
             $old_post = $post;
783
-            $post     = get_post($result[ $model->primary_key_name() ]);
784
-            if (! $post instanceof WP_Post) {
783
+            $post     = get_post($result[$model->primary_key_name()]);
784
+            if ( ! $post instanceof WP_Post) {
785 785
                 // well that's weird, because $result is what we JUST fetched from the database
786 786
                 throw new RestException(
787 787
                     'error_fetching_post_from_database_results',
@@ -791,7 +791,7 @@  discard block
 block discarded – undo
791 791
                     )
792 792
                 );
793 793
             }
794
-            $model_object_classname          = 'EE_' . $model->get_this_model_name();
794
+            $model_object_classname          = 'EE_'.$model->get_this_model_name();
795 795
             $post->{$model_object_classname} = EE_Registry::instance()->load_class(
796 796
                 $model_object_classname,
797 797
                 $result,
@@ -802,14 +802,14 @@  discard block
 block discarded – undo
802 802
         foreach ($result as $field_name => $field_value) {
803 803
             $field_obj = $model->field_settings_for($field_name);
804 804
             if ($this->isSubclassOfOne($field_obj, $this->getModelVersionInfo()->fieldsIgnored())) {
805
-                unset($result[ $field_name ]);
805
+                unset($result[$field_name]);
806 806
             } elseif (
807 807
                 $this->isSubclassOfOne(
808 808
                     $field_obj,
809 809
                     $this->getModelVersionInfo()->fieldsThatHaveRenderedFormat()
810 810
                 )
811 811
             ) {
812
-                $result[ $field_name ] = [
812
+                $result[$field_name] = [
813 813
                     'raw'      => $this->prepareFieldObjValueForJson($field_obj, $field_value),
814 814
                     'rendered' => $this->prepareFieldObjValueForJson($field_obj, $field_value, 'pretty'),
815 815
                 ];
@@ -819,7 +819,7 @@  discard block
 block discarded – undo
819 819
                     $this->getModelVersionInfo()->fieldsThatHavePrettyFormat()
820 820
                 )
821 821
             ) {
822
-                $result[ $field_name ] = [
822
+                $result[$field_name] = [
823 823
                     'raw'    => $this->prepareFieldObjValueForJson($field_obj, $field_value),
824 824
                     'pretty' => $this->prepareFieldObjValueForJson($field_obj, $field_value, 'pretty'),
825 825
                 ];
@@ -850,10 +850,10 @@  discard block
 block discarded – undo
850 850
                         $this->getModelVersionInfo()->requestedVersion()
851 851
                     );
852 852
                 }
853
-                $result[ $field_name . '_gmt' ] = $gmt_date;
854
-                $result[ $field_name ]          = $local_date;
853
+                $result[$field_name.'_gmt'] = $gmt_date;
854
+                $result[$field_name]          = $local_date;
855 855
             } else {
856
-                $result[ $field_name ] = $this->prepareFieldObjValueForJson($field_obj, $field_value);
856
+                $result[$field_name] = $this->prepareFieldObjValueForJson($field_obj, $field_value);
857 857
             }
858 858
         }
859 859
         if ($do_chevy_shuffle) {
@@ -911,7 +911,7 @@  discard block
 block discarded – undo
911 911
     protected function addExtraFields(EEM_Base $model, array $db_row, array $entity_array): array
912 912
     {
913 913
         if ($model instanceof EEM_CPT_Base) {
914
-            $entity_array['link'] = get_permalink($db_row[ $model->get_primary_key_field()->get_qualified_column() ]);
914
+            $entity_array['link'] = get_permalink($db_row[$model->get_primary_key_field()->get_qualified_column()]);
915 915
         }
916 916
         return $entity_array;
917 917
     }
@@ -938,7 +938,7 @@  discard block
 block discarded – undo
938 938
                     'href' => $this->getVersionedLinkTo(
939 939
                         EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
940 940
                         . '/'
941
-                        . $entity_array[ $model->primary_key_name() ]
941
+                        . $entity_array[$model->primary_key_name()]
942 942
                     ),
943 943
                 ],
944 944
             ];
@@ -955,12 +955,12 @@  discard block
 block discarded – undo
955 955
             foreach ($this->getModelVersionInfo()->relationSettings($model) as $relation_name => $relation_obj) {
956 956
                 $related_model_part                                                      =
957 957
                     Read::getRelatedEntityName($relation_name, $relation_obj);
958
-                $links[ EED_Core_Rest_Api::ee_api_link_namespace . $related_model_part ] = [
958
+                $links[EED_Core_Rest_Api::ee_api_link_namespace.$related_model_part] = [
959 959
                     [
960 960
                         'href'   => $this->getVersionedLinkTo(
961 961
                             EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
962 962
                             . '/'
963
-                            . $entity_array[ $model->primary_key_name() ]
963
+                            . $entity_array[$model->primary_key_name()]
964 964
                             . '/'
965 965
                             . $related_model_part
966 966
                         ),
@@ -994,12 +994,12 @@  discard block
 block discarded – undo
994 994
         bool $included_items_protected = false
995 995
     ): array {
996 996
         // if $db_row not included, hope the entity array has what we need
997
-        if (! $db_row) {
997
+        if ( ! $db_row) {
998 998
             $db_row = $entity_array;
999 999
         }
1000 1000
         $relation_settings = $this->getModelVersionInfo()->relationSettings($model);
1001 1001
         foreach ($relation_settings as $relation_name => $relation_obj) {
1002
-            $related_fields_to_include   = $this->explodeAndGetItemsPrefixedWith(
1002
+            $related_fields_to_include = $this->explodeAndGetItemsPrefixedWith(
1003 1003
                 $rest_request->get_param('include'),
1004 1004
                 $relation_name
1005 1005
             );
@@ -1027,7 +1027,7 @@  discard block
 block discarded – undo
1027 1027
                         $model->deduce_fields_n_values_from_cols_n_values($db_row)
1028 1028
                     )
1029 1029
                 );
1030
-                if (! $included_items_protected) {
1030
+                if ( ! $included_items_protected) {
1031 1031
                     try {
1032 1032
                         $related_results = $this->getEntitiesFromRelationUsingModelQueryParams(
1033 1033
                             $primary_model_query_params,
@@ -1047,7 +1047,7 @@  discard block
 block discarded – undo
1047 1047
                         ? null
1048 1048
                         : [];
1049 1049
                 }
1050
-                $entity_array[ Read::getRelatedEntityName($relation_name, $relation_obj) ] = $related_results;
1050
+                $entity_array[Read::getRelatedEntityName($relation_name, $relation_obj)] = $related_results;
1051 1051
             }
1052 1052
         }
1053 1053
         return $entity_array;
@@ -1134,13 +1134,13 @@  discard block
 block discarded – undo
1134 1134
                 $schema = $this->fields_calculator->getJsonSchemaForModel($model);
1135 1135
                 if (
1136 1136
                     $row_is_protected
1137
-                    && isset($schema['properties'][ $field_to_calculate ]['protected'])
1138
-                    && $schema['properties'][ $field_to_calculate ]['protected']
1137
+                    && isset($schema['properties'][$field_to_calculate]['protected'])
1138
+                    && $schema['properties'][$field_to_calculate]['protected']
1139 1139
                 ) {
1140 1140
                     $calculated_value   = null;
1141 1141
                     $protected_fields[] = $field_to_calculate;
1142
-                    if ($schema['properties'][ $field_to_calculate ]['type']) {
1143
-                        switch ($schema['properties'][ $field_to_calculate ]['type']) {
1142
+                    if ($schema['properties'][$field_to_calculate]['type']) {
1143
+                        switch ($schema['properties'][$field_to_calculate]['type']) {
1144 1144
                             case 'boolean':
1145 1145
                                 $calculated_value = false;
1146 1146
                                 break;
@@ -1254,7 +1254,7 @@  discard block
 block discarded – undo
1254 1254
      */
1255 1255
     public function validateContext(string $context): string
1256 1256
     {
1257
-        if (! $context) {
1257
+        if ( ! $context) {
1258 1258
             $context = EEM_Base::caps_read;
1259 1259
         }
1260 1260
         $valid_contexts = EEM_Base::valid_cap_contexts();
@@ -1279,7 +1279,7 @@  discard block
 block discarded – undo
1279 1279
             EEM_Base::default_where_conditions_minimum_all,
1280 1280
             EEM_Base::default_where_conditions_minimum_others,
1281 1281
         ];
1282
-        if (! $default_query_params) {
1282
+        if ( ! $default_query_params) {
1283 1283
             $default_query_params = EEM_Base::default_where_conditions_all;
1284 1284
         }
1285 1285
         if (
@@ -1366,14 +1366,14 @@  discard block
 block discarded – undo
1366 1366
         }
1367 1367
         if (isset($query_params['limit'])) {
1368 1368
             // limit should be either a string like '23' or '23,43', or an array with two items in it
1369
-            if (! is_array($query_params['limit'])) {
1369
+            if ( ! is_array($query_params['limit'])) {
1370 1370
                 $limit_array = explode(',', (string) $query_params['limit']);
1371 1371
             } else {
1372 1372
                 $limit_array = $query_params['limit'];
1373 1373
             }
1374 1374
             $sanitized_limit = [];
1375 1375
             foreach ($limit_array as $limit_part) {
1376
-                if ($this->debug_mode && (! is_numeric($limit_part) || count($sanitized_limit) > 2)) {
1376
+                if ($this->debug_mode && ( ! is_numeric($limit_part) || count($sanitized_limit) > 2)) {
1377 1377
                     throw new EE_Error(
1378 1378
                         sprintf(
1379 1379
                             esc_html__(
@@ -1431,7 +1431,7 @@  discard block
 block discarded – undo
1431 1431
     {
1432 1432
         $model_ready_query_params = [];
1433 1433
         foreach ($query_params as $key => $value) {
1434
-            $model_ready_query_params[ $key ] = is_array($value)
1434
+            $model_ready_query_params[$key] = is_array($value)
1435 1435
                 ? $this->prepareRestQueryParamsKeyForModels($model, $value)
1436 1436
                 : $value;
1437 1437
         }
@@ -1450,9 +1450,9 @@  discard block
 block discarded – undo
1450 1450
         $model_ready_query_params = [];
1451 1451
         foreach ($query_params as $key => $value) {
1452 1452
             if (is_array($value)) {
1453
-                $model_ready_query_params[ $key ] = $this->prepareRestQueryParamsValuesForModels($model, $value);
1453
+                $model_ready_query_params[$key] = $this->prepareRestQueryParamsValuesForModels($model, $value);
1454 1454
             } else {
1455
-                $model_ready_query_params[ $key ] = $value;
1455
+                $model_ready_query_params[$key] = $value;
1456 1456
             }
1457 1457
         }
1458 1458
         return $model_ready_query_params;
@@ -1484,17 +1484,17 @@  discard block
 block discarded – undo
1484 1484
         foreach ($exploded_contents as $item) {
1485 1485
             $item = trim($item);
1486 1486
             // if no prefix was provided, so we look for items with no "." in them
1487
-            if (! $prefix) {
1487
+            if ( ! $prefix) {
1488 1488
                 // does this item have a period?
1489 1489
                 if (strpos($item, '.') === false) {
1490 1490
                     // if not, then its what we're looking for
1491 1491
                     $contents_with_prefix[] = $item;
1492 1492
                 }
1493
-            } elseif (strpos($item, $prefix . '.') === 0) {
1493
+            } elseif (strpos($item, $prefix.'.') === 0) {
1494 1494
                 // this item has the prefix and a period, grab it
1495 1495
                 $contents_with_prefix[] = substr(
1496 1496
                     $item,
1497
-                    strpos($item, $prefix . '.') + strlen($prefix . '.')
1497
+                    strpos($item, $prefix.'.') + strlen($prefix.'.')
1498 1498
                 );
1499 1499
             } elseif ($item === $prefix) {
1500 1500
                 // this item is JUST the prefix
@@ -1536,9 +1536,9 @@  discard block
 block discarded – undo
1536 1536
         if ($model_name) {
1537 1537
             foreach ($includes as $field_to_include) {
1538 1538
                 $field_to_include = trim($field_to_include);
1539
-                if (strpos($field_to_include, $model_name . '.') === 0) {
1539
+                if (strpos($field_to_include, $model_name.'.') === 0) {
1540 1540
                     // found the model name at the exact start
1541
-                    $field_sans_model_name         = str_replace($model_name . '.', '', $field_to_include);
1541
+                    $field_sans_model_name         = str_replace($model_name.'.', '', $field_to_include);
1542 1542
                     $extracted_fields_to_include[] = $field_sans_model_name;
1543 1543
                 } elseif ($field_to_include == $model_name) {
1544 1544
                     $extracted_fields_to_include[] = '*';
@@ -1581,7 +1581,7 @@  discard block
 block discarded – undo
1581 1581
         $restricted_query_params['caps'] = $context;
1582 1582
         $this->setDebugInfo('model query params', $restricted_query_params);
1583 1583
         $model_rows = $model->get_all_wpdb_results($restricted_query_params);
1584
-        if (! empty($model_rows)) {
1584
+        if ( ! empty($model_rows)) {
1585 1585
             return $this->createEntityFromWpdbResult(
1586 1586
                 $model,
1587 1587
                 reset($model_rows),
@@ -1593,7 +1593,7 @@  discard block
 block discarded – undo
1593 1593
             if ($model->exists($query_params)) {
1594 1594
                 // you got shafted- it existed but we didn't want to tell you!
1595 1595
                 throw new RestException(
1596
-                    'rest_user_cannot_' . $context,
1596
+                    'rest_user_cannot_'.$context,
1597 1597
                     sprintf(
1598 1598
                         esc_html__('Sorry, you cannot %1$s this %2$s. Missing permissions are: %3$s', 'event_espresso'),
1599 1599
                         $context,
@@ -1647,14 +1647,14 @@  discard block
 block discarded – undo
1647 1647
         // if this entity requires a password, they better give it and it better be right!
1648 1648
         if (
1649 1649
             $model->hasPassword()
1650
-            && $model_row[ $model->getPasswordField()->get_qualified_column() ] !== ''
1650
+            && $model_row[$model->getPasswordField()->get_qualified_column()] !== ''
1651 1651
         ) {
1652 1652
             if (empty($request['password'])) {
1653 1653
                 throw new RestPasswordRequiredException();
1654 1654
             }
1655 1655
             if (
1656 1656
                 ! hash_equals(
1657
-                    $model_row[ $model->getPasswordField()->get_qualified_column() ],
1657
+                    $model_row[$model->getPasswordField()->get_qualified_column()],
1658 1658
                     $request['password']
1659 1659
                 )
1660 1660
             ) {
@@ -1668,12 +1668,12 @@  discard block
 block discarded – undo
1668 1668
             $password_supplied = $request->get_param('password');
1669 1669
             if (empty($password_supplied)) {
1670 1670
                 $query_params['exclude_protected'] = true;
1671
-                if (! $model->exists($query_params)) {
1671
+                if ( ! $model->exists($query_params)) {
1672 1672
                     throw new RestPasswordRequiredException();
1673 1673
                 }
1674 1674
             } else {
1675
-                $query_params[0][ $model->modelChainAndPassword() ] = $password_supplied;
1676
-                if (! $model->exists($query_params)) {
1675
+                $query_params[0][$model->modelChainAndPassword()] = $password_supplied;
1676
+                if ( ! $model->exists($query_params)) {
1677 1677
                     throw new RestPasswordIncorrectException();
1678 1678
                 }
1679 1679
             }
Please login to merge, or discard this patch.