Completed
Branch FET/recalculate-line-items (beaf35)
by
unknown
26:03 queued 17:44
created
core/libraries/rest_api/controllers/model/Read.php 2 patches
Indentation   +1554 added lines, -1554 removed lines patch added patch discarded remove patch
@@ -45,1558 +45,1558 @@
 block discarded – undo
45 45
 {
46 46
 
47 47
 
48
-    /**
49
-     * @var CalculatedModelFields
50
-     */
51
-    protected $fields_calculator;
52
-
53
-
54
-    /**
55
-     * Read constructor.
56
-     * @param CalculatedModelFields $fields_calculator
57
-     */
58
-    public function __construct(CalculatedModelFields $fields_calculator)
59
-    {
60
-        parent::__construct();
61
-        $this->fields_calculator = $fields_calculator;
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|WP_Error
72
-     * @throws InvalidArgumentException
73
-     * @throws InvalidDataTypeException
74
-     * @throws InvalidInterfaceException
75
-     */
76
-    public static function handleRequestGetAll(WP_REST_Request $request, $version, $model_name)
77
-    {
78
-        $controller = LoaderFactory::getLoader()->getNew('EventEspresso\core\libraries\rest_api\controllers\model\Read');
79
-        try {
80
-            $controller->setRequestedVersion($version);
81
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
82
-                return $controller->sendResponse(
83
-                    new WP_Error(
84
-                        'endpoint_parsing_error',
85
-                        sprintf(
86
-                            __(
87
-                                'There is no model for endpoint %s. Please contact event espresso support',
88
-                                'event_espresso'
89
-                            ),
90
-                            $model_name
91
-                        )
92
-                    )
93
-                );
94
-            }
95
-            return $controller->sendResponse(
96
-                $controller->getEntitiesFromModel(
97
-                    $controller->getModelVersionInfo()->loadModel($model_name),
98
-                    $request
99
-                )
100
-            );
101
-        } catch (Exception $e) {
102
-            return $controller->sendResponse($e);
103
-        }
104
-    }
105
-
106
-
107
-    /**
108
-     * Prepares and returns schema for any OPTIONS request.
109
-     *
110
-     * @param string $version The API endpoint version being used.
111
-     * @param string $model_name Something like `Event` or `Registration`
112
-     * @return array
113
-     * @throws InvalidArgumentException
114
-     * @throws InvalidDataTypeException
115
-     * @throws InvalidInterfaceException
116
-     */
117
-    public static function handleSchemaRequest($version, $model_name)
118
-    {
119
-        $controller = LoaderFactory::getLoader()->getNew('EventEspresso\core\libraries\rest_api\controllers\model\Read');
120
-        try {
121
-            $controller->setRequestedVersion($version);
122
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
123
-                return array();
124
-            }
125
-            // get the model for this version
126
-            $model = $controller->getModelVersionInfo()->loadModel($model_name);
127
-            $model_schema = new JsonModelSchema($model, LoaderFactory::getLoader()->getShared('EventEspresso\core\libraries\rest_api\CalculatedModelFields'));
128
-            return $model_schema->getModelSchemaForRelations(
129
-                $controller->getModelVersionInfo()->relationSettings($model),
130
-                $controller->customizeSchemaForRestResponse(
131
-                    $model,
132
-                    $model_schema->getModelSchemaForFields(
133
-                        $controller->getModelVersionInfo()->fieldsOnModelInThisVersion($model),
134
-                        $model_schema->getInitialSchemaStructure()
135
-                    )
136
-                )
137
-            );
138
-        } catch (Exception $e) {
139
-            return array();
140
-        }
141
-    }
142
-
143
-
144
-    /**
145
-     * This loops through each field in the given schema for the model and does the following:
146
-     * - add any extra fields that are REST API specific and related to existing fields.
147
-     * - transform default values into the correct format for a REST API response.
148
-     *
149
-     * @param EEM_Base $model
150
-     * @param array    $schema
151
-     * @return array  The final schema.
152
-     */
153
-    protected function customizeSchemaForRestResponse(EEM_Base $model, array $schema)
154
-    {
155
-        foreach ($this->getModelVersionInfo()->fieldsOnModelInThisVersion($model) as $field_name => $field) {
156
-            $schema = $this->translateDefaultsForRestResponse(
157
-                $field_name,
158
-                $field,
159
-                $this->maybeAddExtraFieldsToSchema($field_name, $field, $schema)
160
-            );
161
-        }
162
-        return $schema;
163
-    }
164
-
165
-
166
-    /**
167
-     * This is used to ensure that the 'default' value set in the schema response is formatted correctly for the REST
168
-     * response.
169
-     *
170
-     * @param                      $field_name
171
-     * @param EE_Model_Field_Base  $field
172
-     * @param array                $schema
173
-     * @return array
174
-     * @throws ObjectDetectedException if a default value has a PHP object, which should never do (and if we
175
-     * did, let's know about it ASAP, so let the exception bubble up)
176
-     */
177
-    protected function translateDefaultsForRestResponse($field_name, EE_Model_Field_Base $field, array $schema)
178
-    {
179
-        if (isset($schema['properties'][ $field_name ]['default'])) {
180
-            if (is_array($schema['properties'][ $field_name ]['default'])) {
181
-                foreach ($schema['properties'][ $field_name ]['default'] as $default_key => $default_value) {
182
-                    if ($default_key === 'raw') {
183
-                        $schema['properties'][ $field_name ]['default'][ $default_key ] =
184
-                            ModelDataTranslator::prepareFieldValueForJson(
185
-                                $field,
186
-                                $default_value,
187
-                                $this->getModelVersionInfo()->requestedVersion()
188
-                            );
189
-                    }
190
-                }
191
-            } else {
192
-                $schema['properties'][ $field_name ]['default'] = ModelDataTranslator::prepareFieldValueForJson(
193
-                    $field,
194
-                    $schema['properties'][ $field_name ]['default'],
195
-                    $this->getModelVersionInfo()->requestedVersion()
196
-                );
197
-            }
198
-        }
199
-        return $schema;
200
-    }
201
-
202
-
203
-    /**
204
-     * Adds additional fields to the schema
205
-     * The REST API returns a GMT value field for each datetime field in the resource.  Thus the description about this
206
-     * needs to be added to the schema.
207
-     *
208
-     * @param                      $field_name
209
-     * @param EE_Model_Field_Base  $field
210
-     * @param array                $schema
211
-     * @return array
212
-     */
213
-    protected function maybeAddExtraFieldsToSchema($field_name, EE_Model_Field_Base $field, array $schema)
214
-    {
215
-        if ($field instanceof EE_Datetime_Field) {
216
-            $schema['properties'][ $field_name . '_gmt' ] = $field->getSchema();
217
-            // modify the description
218
-            $schema['properties'][ $field_name . '_gmt' ]['description'] = sprintf(
219
-                esc_html__('%s - the value for this field is in GMT.', 'event_espresso'),
220
-                wp_specialchars_decode($field->get_nicename(), ENT_QUOTES)
221
-            );
222
-        }
223
-        return $schema;
224
-    }
225
-
226
-
227
-    /**
228
-     * Used to figure out the route from the request when a `WP_REST_Request` object is not available
229
-     *
230
-     * @return string
231
-     */
232
-    protected function getRouteFromRequest()
233
-    {
234
-        if (isset($GLOBALS['wp'])
235
-            && $GLOBALS['wp'] instanceof \WP
236
-            && isset($GLOBALS['wp']->query_vars['rest_route'])
237
-        ) {
238
-            return $GLOBALS['wp']->query_vars['rest_route'];
239
-        } else {
240
-            return isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : '/';
241
-        }
242
-    }
243
-
244
-
245
-    /**
246
-     * Gets a single entity related to the model indicated in the path and its id
247
-     *
248
-     * @param WP_REST_Request $request
249
-     * @param string $version
250
-     * @param string $model_name
251
-     * @return WP_REST_Response|WP_Error
252
-     * @throws InvalidDataTypeException
253
-     * @throws InvalidInterfaceException
254
-     * @throws InvalidArgumentException
255
-     */
256
-    public static function handleRequestGetOne(WP_REST_Request $request, $version, $model_name)
257
-    {
258
-        $controller = LoaderFactory::getLoader()->getNew('EventEspresso\core\libraries\rest_api\controllers\model\Read');
259
-        try {
260
-            $controller->setRequestedVersion($version);
261
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
262
-                return $controller->sendResponse(
263
-                    new WP_Error(
264
-                        'endpoint_parsing_error',
265
-                        sprintf(
266
-                            __(
267
-                                'There is no model for endpoint %s. Please contact event espresso support',
268
-                                'event_espresso'
269
-                            ),
270
-                            $model_name
271
-                        )
272
-                    )
273
-                );
274
-            }
275
-            return $controller->sendResponse(
276
-                $controller->getEntityFromModel(
277
-                    $controller->getModelVersionInfo()->loadModel($model_name),
278
-                    $request
279
-                )
280
-            );
281
-        } catch (Exception $e) {
282
-            return $controller->sendResponse($e);
283
-        }
284
-    }
285
-
286
-
287
-    /**
288
-     * Gets all the related entities (or if its a belongs-to relation just the one)
289
-     * to the item with the given id
290
-     *
291
-     * @param WP_REST_Request $request
292
-     * @param string $version
293
-     * @param string $model_name
294
-     * @param string $related_model_name
295
-     * @return WP_REST_Response|WP_Error
296
-     * @throws InvalidDataTypeException
297
-     * @throws InvalidInterfaceException
298
-     * @throws InvalidArgumentException
299
-     */
300
-    public static function handleRequestGetRelated(
301
-        WP_REST_Request $request,
302
-        $version,
303
-        $model_name,
304
-        $related_model_name
305
-    ) {
306
-        $controller = LoaderFactory::getLoader()->getNew('EventEspresso\core\libraries\rest_api\controllers\model\Read');
307
-        try {
308
-            $controller->setRequestedVersion($version);
309
-            $main_model = $controller->validateModel($model_name);
310
-            $controller->validateModel($related_model_name);
311
-            return $controller->sendResponse(
312
-                $controller->getEntitiesFromRelation(
313
-                    $request->get_param('id'),
314
-                    $main_model->related_settings_for($related_model_name),
315
-                    $request
316
-                )
317
-            );
318
-        } catch (Exception $e) {
319
-            return $controller->sendResponse($e);
320
-        }
321
-    }
322
-
323
-
324
-    /**
325
-     * Gets a collection for the given model and filters
326
-     *
327
-     * @param EEM_Base $model
328
-     * @param WP_REST_Request $request
329
-     * @return array
330
-     * @throws EE_Error
331
-     * @throws InvalidArgumentException
332
-     * @throws InvalidDataTypeException
333
-     * @throws InvalidInterfaceException
334
-     * @throws ReflectionException
335
-     * @throws RestException
336
-     */
337
-    public function getEntitiesFromModel($model, $request)
338
-    {
339
-        $query_params = $this->createModelQueryParams($model, $request->get_params());
340
-        if (! Capabilities::currentUserHasPartialAccessTo($model, $query_params['caps'])) {
341
-            $model_name_plural = EEH_Inflector::pluralize_and_lower($model->get_this_model_name());
342
-            throw new RestException(
343
-                sprintf('rest_%s_cannot_list', $model_name_plural),
344
-                sprintf(
345
-                    __('Sorry, you are not allowed to list %1$s. Missing permissions: %2$s', 'event_espresso'),
346
-                    $model_name_plural,
347
-                    Capabilities::getMissingPermissionsString($model, $query_params['caps'])
348
-                ),
349
-                array('status' => 403)
350
-            );
351
-        }
352
-        if (! $request->get_header('no_rest_headers')) {
353
-            $this->setHeadersFromQueryParams($model, $query_params);
354
-        }
355
-        /** @type array $results */
356
-        $results = $model->get_all_wpdb_results($query_params);
357
-        $nice_results = array();
358
-        foreach ($results as $result) {
359
-            $nice_results[] =  $this->createEntityFromWpdbResult(
360
-                $model,
361
-                $result,
362
-                $request
363
-            );
364
-        }
365
-        return $nice_results;
366
-    }
367
-
368
-
369
-    /**
370
-     * Gets the collection for given relation object
371
-     * The same as Read::get_entities_from_model(), except if the relation
372
-     * is a HABTM relation, in which case it merges any non-foreign-key fields from
373
-     * the join-model-object into the results
374
-     *
375
-     * @param array $primary_model_query_params query params for finding the item from which
376
-     *                                                            relations will be based
377
-     * @param \EE_Model_Relation_Base $relation
378
-     * @param WP_REST_Request $request
379
-     * @return array
380
-     * @throws EE_Error
381
-     * @throws InvalidArgumentException
382
-     * @throws InvalidDataTypeException
383
-     * @throws InvalidInterfaceException
384
-     * @throws ReflectionException
385
-     * @throws RestException
386
-     * @throws \EventEspresso\core\exceptions\ModelConfigurationException
387
-     */
388
-    protected function getEntitiesFromRelationUsingModelQueryParams($primary_model_query_params, $relation, $request)
389
-    {
390
-        $context = $this->validateContext($request->get_param('caps'));
391
-        $model = $relation->get_this_model();
392
-        $related_model = $relation->get_other_model();
393
-        if (! isset($primary_model_query_params[0])) {
394
-            $primary_model_query_params[0] = array();
395
-        }
396
-        // check if they can access the 1st model object
397
-        $primary_model_query_params = array(
398
-            0       => $primary_model_query_params[0],
399
-            'limit' => 1,
400
-        );
401
-        if ($model instanceof EEM_Soft_Delete_Base) {
402
-            $primary_model_query_params = $model->alter_query_params_so_deleted_and_undeleted_items_included(
403
-                $primary_model_query_params
404
-            );
405
-        }
406
-        $restricted_query_params = $primary_model_query_params;
407
-        $restricted_query_params['caps'] = $context;
408
-        $restricted_query_params['limit'] = 1;
409
-        $this->setDebugInfo('main model query params', $restricted_query_params);
410
-        $this->setDebugInfo('missing caps', Capabilities::getMissingPermissionsString($related_model, $context));
411
-        $primary_model_rows = $model->get_all_wpdb_results($restricted_query_params);
412
-        $primary_model_row = null;
413
-        if (is_array($primary_model_rows)) {
414
-            $primary_model_row = reset($primary_model_rows);
415
-        }
416
-        if (! (
417
-            Capabilities::currentUserHasPartialAccessTo($related_model, $context)
418
-            && $primary_model_row
419
-        )
420
-        ) {
421
-            if ($relation instanceof EE_Belongs_To_Relation) {
422
-                $related_model_name_maybe_plural = strtolower($related_model->get_this_model_name());
423
-            } else {
424
-                $related_model_name_maybe_plural = EEH_Inflector::pluralize_and_lower(
425
-                    $related_model->get_this_model_name()
426
-                );
427
-            }
428
-            throw new RestException(
429
-                sprintf('rest_%s_cannot_list', $related_model_name_maybe_plural),
430
-                sprintf(
431
-                    __(
432
-                        'Sorry, you are not allowed to list %1$s related to %2$s. Missing permissions: %3$s',
433
-                        'event_espresso'
434
-                    ),
435
-                    $related_model_name_maybe_plural,
436
-                    $relation->get_this_model()->get_this_model_name(),
437
-                    implode(
438
-                        ',',
439
-                        array_keys(
440
-                            Capabilities::getMissingPermissions($related_model, $context)
441
-                        )
442
-                    )
443
-                ),
444
-                array('status' => 403)
445
-            );
446
-        }
447
-
448
-        $this->checkPassword(
449
-            $model,
450
-            $primary_model_row,
451
-            $restricted_query_params,
452
-            $request
453
-        );
454
-        $query_params = $this->createModelQueryParams($relation->get_other_model(), $request->get_params());
455
-        foreach ($primary_model_query_params[0] as $where_condition_key => $where_condition_value) {
456
-            $query_params[0][ $relation->get_this_model()->get_this_model_name()
457
-                              . '.'
458
-                              . $where_condition_key ] = $where_condition_value;
459
-        }
460
-        $query_params['default_where_conditions'] = 'none';
461
-        $query_params['caps'] = $context;
462
-        if (! $request->get_header('no_rest_headers')) {
463
-            $this->setHeadersFromQueryParams($relation->get_other_model(), $query_params);
464
-        }
465
-        /** @type array $results */
466
-        $results = $relation->get_other_model()->get_all_wpdb_results($query_params);
467
-        $nice_results = array();
468
-        foreach ($results as $result) {
469
-            $nice_result = $this->createEntityFromWpdbResult(
470
-                $relation->get_other_model(),
471
-                $result,
472
-                $request
473
-            );
474
-            if ($relation instanceof \EE_HABTM_Relation) {
475
-                // put the unusual stuff (properties from the HABTM relation) first, and make sure
476
-                // if there are conflicts we prefer the properties from the main model
477
-                $join_model_result = $this->createEntityFromWpdbResult(
478
-                    $relation->get_join_model(),
479
-                    $result,
480
-                    $request
481
-                );
482
-                $joined_result = array_merge($nice_result, $join_model_result);
483
-                // but keep the meta stuff from the main model
484
-                if (isset($nice_result['meta'])) {
485
-                    $joined_result['meta'] = $nice_result['meta'];
486
-                }
487
-                $nice_result = $joined_result;
488
-            }
489
-            $nice_results[] = $nice_result;
490
-        }
491
-        if ($relation instanceof EE_Belongs_To_Relation) {
492
-            return array_shift($nice_results);
493
-        } else {
494
-            return $nice_results;
495
-        }
496
-    }
497
-
498
-
499
-    /**
500
-     * Gets the collection for given relation object
501
-     * The same as Read::get_entities_from_model(), except if the relation
502
-     * is a HABTM relation, in which case it merges any non-foreign-key fields from
503
-     * the join-model-object into the results
504
-     *
505
-     * @param string                  $id the ID of the thing we are fetching related stuff from
506
-     * @param \EE_Model_Relation_Base $relation
507
-     * @param WP_REST_Request         $request
508
-     * @return array
509
-     * @throws EE_Error
510
-     */
511
-    public function getEntitiesFromRelation($id, $relation, $request)
512
-    {
513
-        if (! $relation->get_this_model()->has_primary_key_field()) {
514
-            throw new EE_Error(
515
-                sprintf(
516
-                    __(
517
-                    // @codingStandardsIgnoreStart
518
-                        'Read::get_entities_from_relation should only be called from a model with a primary key, it was called from %1$s',
519
-                        // @codingStandardsIgnoreEnd
520
-                        'event_espresso'
521
-                    ),
522
-                    $relation->get_this_model()->get_this_model_name()
523
-                )
524
-            );
525
-        }
526
-        // can we edit that main item?
527
-        // if not, show nothing but an error
528
-        // otherwise, please proceed
529
-        return $this->getEntitiesFromRelationUsingModelQueryParams(
530
-            array(
531
-                array(
532
-                    $relation->get_this_model()->primary_key_name() => $id,
533
-                ),
534
-            ),
535
-            $relation,
536
-            $request
537
-        );
538
-    }
539
-
540
-
541
-    /**
542
-     * Sets the headers that are based on the model and query params,
543
-     * like the total records. This should only be called on the original request
544
-     * from the client, not on subsequent internal
545
-     *
546
-     * @param EEM_Base $model
547
-     * @param array    $query_params
548
-     * @return void
549
-     */
550
-    protected function setHeadersFromQueryParams($model, $query_params)
551
-    {
552
-        $this->setDebugInfo('model query params', $query_params);
553
-        $this->setDebugInfo(
554
-            'missing caps',
555
-            Capabilities::getMissingPermissionsString($model, $query_params['caps'])
556
-        );
557
-        // normally the limit to a 2-part array, where the 2nd item is the limit
558
-        if (! isset($query_params['limit'])) {
559
-            $query_params['limit'] = EED_Core_Rest_Api::get_default_query_limit();
560
-        }
561
-        if (is_array($query_params['limit'])) {
562
-            $limit_parts = $query_params['limit'];
563
-        } else {
564
-            $limit_parts = explode(',', $query_params['limit']);
565
-            if (count($limit_parts) == 1) {
566
-                $limit_parts = array(0, $limit_parts[0]);
567
-            }
568
-        }
569
-        // remove the group by and having parts of the query, as those will
570
-        // make the sql query return an array of values, instead of just a single value
571
-        unset($query_params['group_by'], $query_params['having'], $query_params['limit']);
572
-        $count = $model->count($query_params, null, true);
573
-        $pages = $count / $limit_parts[1];
574
-        $this->setResponseHeader('Total', $count, false);
575
-        $this->setResponseHeader('PageSize', $limit_parts[1], false);
576
-        $this->setResponseHeader('TotalPages', ceil($pages), false);
577
-    }
578
-
579
-
580
-    /**
581
-     * Changes database results into REST API entities
582
-     *
583
-     * @param EEM_Base $model
584
-     * @param array $db_row like results from $wpdb->get_results()
585
-     * @param WP_REST_Request $rest_request
586
-     * @param string $deprecated no longer used
587
-     * @return array ready for being converted into json for sending to client
588
-     * @throws EE_Error
589
-     * @throws RestException
590
-     * @throws InvalidDataTypeException
591
-     * @throws InvalidInterfaceException
592
-     * @throws InvalidArgumentException
593
-     * @throws ReflectionException
594
-     */
595
-    public function createEntityFromWpdbResult($model, $db_row, $rest_request, $deprecated = null)
596
-    {
597
-        if (! $rest_request instanceof WP_REST_Request) {
598
-            // ok so this was called in the old style, where the 3rd arg was
599
-            // $include, and the 4th arg was $context
600
-            // now setup the request just to avoid fatal errors, although we won't be able
601
-            // to truly make use of it because it's kinda devoid of info
602
-            $rest_request = new WP_REST_Request();
603
-            $rest_request->set_param('include', $rest_request);
604
-            $rest_request->set_param('caps', $deprecated);
605
-        }
606
-        if ($rest_request->get_param('caps') == null) {
607
-            $rest_request->set_param('caps', EEM_Base::caps_read);
608
-        }
609
-        $current_user_full_access_to_entity = $model->currentUserCan(
610
-            EEM_Base::caps_read_admin,
611
-            $model->deduce_fields_n_values_from_cols_n_values($db_row)
612
-        );
613
-        $entity_array = $this->createBareEntityFromWpdbResults($model, $db_row);
614
-        $entity_array = $this->addExtraFields($model, $db_row, $entity_array);
615
-        $entity_array['_links'] = $this->getEntityLinks($model, $db_row, $entity_array);
616
-        // when it's a regular read request for a model with a password and the password wasn't provided
617
-        // remove the password protected fields
618
-        $has_protected_fields = false;
619
-        try {
620
-            $this->checkPassword(
621
-                $model,
622
-                $db_row,
623
-                $model->alter_query_params_to_restrict_by_ID(
624
-                    $model->get_index_primary_key_string(
625
-                        $model->deduce_fields_n_values_from_cols_n_values($db_row)
626
-                    )
627
-                ),
628
-                $rest_request
629
-            );
630
-        } catch (RestPasswordRequiredException $e) {
631
-            if ($model->hasPassword()) {
632
-                // just remove protected fields
633
-                $has_protected_fields = true;
634
-                $entity_array = Capabilities::filterOutPasswordProtectedFields(
635
-                    $entity_array,
636
-                    $model,
637
-                    $this->getModelVersionInfo()
638
-                );
639
-            } else {
640
-                // that's a problem. None of this should be accessible if no password was provided
641
-                throw $e;
642
-            }
643
-        }
644
-
645
-        $entity_array['_calculated_fields'] = $this->getEntityCalculations($model, $db_row, $rest_request, $has_protected_fields);
646
-        $entity_array = apply_filters(
647
-            'FHEE__Read__create_entity_from_wpdb_results__entity_before_including_requested_models',
648
-            $entity_array,
649
-            $model,
650
-            $rest_request->get_param('caps'),
651
-            $rest_request,
652
-            $this
653
-        );
654
-        // add an empty protected property for now. If it's still around after we remove everything the request didn't
655
-        // want, we'll populate it then. k?
656
-        $entity_array['_protected'] = array();
657
-        // remove any properties the request didn't want. This way _protected won't bother mentioning them
658
-        $entity_array = $this->includeOnlyRequestedProperties($model, $rest_request, $entity_array);
659
-        $entity_array = $this->includeRequestedModels($model, $rest_request, $entity_array, $db_row, $has_protected_fields);
660
-        // if they still wanted the _protected property, add it.
661
-        if (isset($entity_array['_protected'])) {
662
-            $entity_array = $this->addProtectedProperty($model, $entity_array, $has_protected_fields);
663
-        }
664
-        $entity_array = apply_filters(
665
-            'FHEE__Read__create_entity_from_wpdb_results__entity_before_inaccessible_field_removal',
666
-            $entity_array,
667
-            $model,
668
-            $rest_request->get_param('caps'),
669
-            $rest_request,
670
-            $this
671
-        );
672
-        if (! $current_user_full_access_to_entity) {
673
-            $result_without_inaccessible_fields = Capabilities::filterOutInaccessibleEntityFields(
674
-                $entity_array,
675
-                $model,
676
-                $rest_request->get_param('caps'),
677
-                $this->getModelVersionInfo()
678
-            );
679
-        } else {
680
-            $result_without_inaccessible_fields = $entity_array;
681
-        }
682
-        $this->setDebugInfo(
683
-            'inaccessible fields',
684
-            array_keys(array_diff_key((array) $entity_array, (array) $result_without_inaccessible_fields))
685
-        );
686
-        return apply_filters(
687
-            'FHEE__Read__create_entity_from_wpdb_results__entity_return',
688
-            $result_without_inaccessible_fields,
689
-            $model,
690
-            $rest_request->get_param('caps')
691
-        );
692
-    }
693
-
694
-    /**
695
-     * Returns an array describing which fields can be protected, and which actually were removed this request
696
-     * @since 4.9.74.p
697
-     * @param $model
698
-     * @param $results_so_far
699
-     * @param $protected
700
-     * @return array results
701
-     */
702
-    protected function addProtectedProperty(EEM_Base $model, $results_so_far, $protected)
703
-    {
704
-        if (! $model->hasPassword() || ! $protected) {
705
-            return $results_so_far;
706
-        }
707
-        $password_field = $model->getPasswordField();
708
-        $all_protected = array_merge(
709
-            array($password_field->get_name()),
710
-            $password_field->protectedFields()
711
-        );
712
-        $fields_included = array_keys($results_so_far);
713
-        $fields_included = array_intersect(
714
-            $all_protected,
715
-            $fields_included
716
-        );
717
-        foreach ($fields_included as $field_name) {
718
-            $results_so_far['_protected'][] = $field_name ;
719
-        }
720
-        return $results_so_far;
721
-    }
722
-
723
-    /**
724
-     * Creates a REST entity array (JSON object we're going to return in the response, but
725
-     * for now still a PHP array, but soon enough we'll call json_encode on it, don't worry),
726
-     * from $wpdb->get_row( $sql, ARRAY_A)
727
-     *
728
-     * @param EEM_Base $model
729
-     * @param array    $db_row
730
-     * @return array entity mostly ready for converting to JSON and sending in the response
731
-     */
732
-    protected function createBareEntityFromWpdbResults(EEM_Base $model, $db_row)
733
-    {
734
-        $result = $model->deduce_fields_n_values_from_cols_n_values($db_row);
735
-        $result = array_intersect_key(
736
-            $result,
737
-            $this->getModelVersionInfo()->fieldsOnModelInThisVersion($model)
738
-        );
739
-        // if this is a CPT, we need to set the global $post to it,
740
-        // otherwise shortcodes etc won't work properly while rendering it
741
-        if ($model instanceof \EEM_CPT_Base) {
742
-            $do_chevy_shuffle = true;
743
-        } else {
744
-            $do_chevy_shuffle = false;
745
-        }
746
-        if ($do_chevy_shuffle) {
747
-            global $post;
748
-            $old_post = $post;
749
-            $post = get_post($result[ $model->primary_key_name() ]);
750
-            if (! $post instanceof \WP_Post) {
751
-                // well that's weird, because $result is what we JUST fetched from the database
752
-                throw new RestException(
753
-                    'error_fetching_post_from_database_results',
754
-                    esc_html__(
755
-                        'An item was retrieved from the database but it\'s not a WP_Post like it should be.',
756
-                        'event_espresso'
757
-                    )
758
-                );
759
-            }
760
-            $model_object_classname = 'EE_' . $model->get_this_model_name();
761
-            $post->{$model_object_classname} = \EE_Registry::instance()->load_class(
762
-                $model_object_classname,
763
-                $result,
764
-                false,
765
-                false
766
-            );
767
-        }
768
-        foreach ($result as $field_name => $field_value) {
769
-            $field_obj = $model->field_settings_for($field_name);
770
-            if ($this->isSubclassOfOne($field_obj, $this->getModelVersionInfo()->fieldsIgnored())) {
771
-                unset($result[ $field_name ]);
772
-            } elseif ($this->isSubclassOfOne(
773
-                $field_obj,
774
-                $this->getModelVersionInfo()->fieldsThatHaveRenderedFormat()
775
-            )
776
-            ) {
777
-                $result[ $field_name ] = array(
778
-                    'raw'      => $this->prepareFieldObjValueForJson($field_obj, $field_value),
779
-                    'rendered' => $this->prepareFieldObjValueForJson($field_obj, $field_value, 'pretty'),
780
-                );
781
-            } elseif ($this->isSubclassOfOne(
782
-                $field_obj,
783
-                $this->getModelVersionInfo()->fieldsThatHavePrettyFormat()
784
-            )
785
-            ) {
786
-                $result[ $field_name ] = array(
787
-                    'raw'    => $this->prepareFieldObjValueForJson($field_obj, $field_value),
788
-                    'pretty' => $this->prepareFieldObjValueForJson($field_obj, $field_value, 'pretty'),
789
-                );
790
-            } elseif ($field_obj instanceof \EE_Datetime_Field) {
791
-                $field_value = $field_obj->prepare_for_set_from_db($field_value);
792
-                // if the value is null, but we're not supposed to permit null, then set to the field's default
793
-                if (is_null($field_value)) {
794
-                    $field_value = $field_obj->getDefaultDateTimeObj();
795
-                }
796
-                if (is_null($field_value)) {
797
-                    $gmt_date = $local_date = ModelDataTranslator::prepareFieldValuesForJson(
798
-                        $field_obj,
799
-                        $field_value,
800
-                        $this->getModelVersionInfo()->requestedVersion()
801
-                    );
802
-                } else {
803
-                    $timezone = $field_value->getTimezone();
804
-                    EEH_DTT_Helper::setTimezone($field_value, new DateTimeZone('UTC'));
805
-                    $gmt_date = ModelDataTranslator::prepareFieldValuesForJson(
806
-                        $field_obj,
807
-                        $field_value,
808
-                        $this->getModelVersionInfo()->requestedVersion()
809
-                    );
810
-                    EEH_DTT_Helper::setTimezone($field_value, $timezone);
811
-                    $local_date = ModelDataTranslator::prepareFieldValuesForJson(
812
-                        $field_obj,
813
-                        $field_value,
814
-                        $this->getModelVersionInfo()->requestedVersion()
815
-                    );
816
-                }
817
-                $result[ $field_name . '_gmt' ] = $gmt_date;
818
-                $result[ $field_name ] = $local_date;
819
-            } else {
820
-                $result[ $field_name ] = $this->prepareFieldObjValueForJson($field_obj, $field_value);
821
-            }
822
-        }
823
-        if ($do_chevy_shuffle) {
824
-            $post = $old_post;
825
-        }
826
-        return $result;
827
-    }
828
-
829
-
830
-    /**
831
-     * Takes a value all the way from the DB representation, to the model object's representation, to the
832
-     * user-facing PHP representation, to the REST API representation. (Assumes you've already taken from the DB
833
-     * representation using $field_obj->prepare_for_set_from_db())
834
-     *
835
-     * @param EE_Model_Field_Base $field_obj
836
-     * @param mixed               $value  as it's stored on a model object
837
-     * @param string              $format valid values are 'normal' (default), 'pretty', 'datetime_obj'
838
-     * @return mixed
839
-     * @throws ObjectDetectedException if $value contains a PHP object
840
-     */
841
-    protected function prepareFieldObjValueForJson(EE_Model_Field_Base $field_obj, $value, $format = 'normal')
842
-    {
843
-        $value = $field_obj->prepare_for_set_from_db($value);
844
-        switch ($format) {
845
-            case 'pretty':
846
-                $value = $field_obj->prepare_for_pretty_echoing($value);
847
-                break;
848
-            case 'normal':
849
-            default:
850
-                $value = $field_obj->prepare_for_get($value);
851
-                break;
852
-        }
853
-        return ModelDataTranslator::prepareFieldValuesForJson(
854
-            $field_obj,
855
-            $value,
856
-            $this->getModelVersionInfo()->requestedVersion()
857
-        );
858
-    }
859
-
860
-
861
-    /**
862
-     * Adds a few extra fields to the entity response
863
-     *
864
-     * @param EEM_Base $model
865
-     * @param array    $db_row
866
-     * @param array    $entity_array
867
-     * @return array modified entity
868
-     */
869
-    protected function addExtraFields(EEM_Base $model, $db_row, $entity_array)
870
-    {
871
-        if ($model instanceof EEM_CPT_Base) {
872
-            $entity_array['link'] = get_permalink($db_row[ $model->get_primary_key_field()->get_qualified_column() ]);
873
-        }
874
-        return $entity_array;
875
-    }
876
-
877
-
878
-    /**
879
-     * Gets links we want to add to the response
880
-     *
881
-     * @global \WP_REST_Server $wp_rest_server
882
-     * @param EEM_Base         $model
883
-     * @param array            $db_row
884
-     * @param array            $entity_array
885
-     * @return array the _links item in the entity
886
-     */
887
-    protected function getEntityLinks($model, $db_row, $entity_array)
888
-    {
889
-        // add basic links
890
-        $links = array();
891
-        if ($model->has_primary_key_field()) {
892
-            $links['self'] = array(
893
-                array(
894
-                    'href' => $this->getVersionedLinkTo(
895
-                        EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
896
-                        . '/'
897
-                        . $entity_array[ $model->primary_key_name() ]
898
-                    ),
899
-                ),
900
-            );
901
-        }
902
-        $links['collection'] = array(
903
-            array(
904
-                'href' => $this->getVersionedLinkTo(
905
-                    EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
906
-                ),
907
-            ),
908
-        );
909
-        // add links to related models
910
-        if ($model->has_primary_key_field()) {
911
-            foreach ($this->getModelVersionInfo()->relationSettings($model) as $relation_name => $relation_obj) {
912
-                $related_model_part = Read::getRelatedEntityName($relation_name, $relation_obj);
913
-                $links[ EED_Core_Rest_Api::ee_api_link_namespace . $related_model_part ] = array(
914
-                    array(
915
-                        'href'   => $this->getVersionedLinkTo(
916
-                            EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
917
-                            . '/'
918
-                            . $entity_array[ $model->primary_key_name() ]
919
-                            . '/'
920
-                            . $related_model_part
921
-                        ),
922
-                        'single' => $relation_obj instanceof EE_Belongs_To_Relation ? true : false,
923
-                    ),
924
-                );
925
-            }
926
-        }
927
-        return $links;
928
-    }
929
-
930
-
931
-    /**
932
-     * Adds the included models indicated in the request to the entity provided
933
-     *
934
-     * @param EEM_Base $model
935
-     * @param WP_REST_Request $rest_request
936
-     * @param array $entity_array
937
-     * @param array $db_row
938
-     * @param boolean $included_items_protected if the original item is password protected, don't include any related models.
939
-     * @return array the modified entity
940
-     * @throws RestException
941
-     */
942
-    protected function includeRequestedModels(
943
-        EEM_Base $model,
944
-        WP_REST_Request $rest_request,
945
-        $entity_array,
946
-        $db_row = array(),
947
-        $included_items_protected = false
948
-    ) {
949
-        // if $db_row not included, hope the entity array has what we need
950
-        if (! $db_row) {
951
-            $db_row = $entity_array;
952
-        }
953
-        $relation_settings = $this->getModelVersionInfo()->relationSettings($model);
954
-        foreach ($relation_settings as $relation_name => $relation_obj) {
955
-            $related_fields_to_include = $this->explodeAndGetItemsPrefixedWith(
956
-                $rest_request->get_param('include'),
957
-                $relation_name
958
-            );
959
-            $related_fields_to_calculate = $this->explodeAndGetItemsPrefixedWith(
960
-                $rest_request->get_param('calculate'),
961
-                $relation_name
962
-            );
963
-            // did they specify they wanted to include a related model, or
964
-            // specific fields from a related model?
965
-            // or did they specify to calculate a field from a related model?
966
-            if ($related_fields_to_include || $related_fields_to_calculate) {
967
-                // if so, we should include at least some part of the related model
968
-                $pretend_related_request = new WP_REST_Request();
969
-                $pretend_related_request->set_query_params(
970
-                    array(
971
-                        'caps'      => $rest_request->get_param('caps'),
972
-                        'include'   => $related_fields_to_include,
973
-                        'calculate' => $related_fields_to_calculate,
974
-                        'password' => $rest_request->get_param('password')
975
-                    )
976
-                );
977
-                $pretend_related_request->add_header('no_rest_headers', true);
978
-                $primary_model_query_params = $model->alter_query_params_to_restrict_by_ID(
979
-                    $model->get_index_primary_key_string(
980
-                        $model->deduce_fields_n_values_from_cols_n_values($db_row)
981
-                    )
982
-                );
983
-                if (! $included_items_protected) {
984
-                    try {
985
-                        $related_results = $this->getEntitiesFromRelationUsingModelQueryParams(
986
-                            $primary_model_query_params,
987
-                            $relation_obj,
988
-                            $pretend_related_request
989
-                        );
990
-                    } catch (RestException $e) {
991
-                        $related_results = null;
992
-                    }
993
-                } else {
994
-                    // they're protected, hide them.
995
-                    $related_results = null;
996
-                    $entity_array['_protected'][] = Read::getRelatedEntityName($relation_name, $relation_obj);
997
-                }
998
-                if ($related_results instanceof WP_Error || $related_results === null) {
999
-                    $related_results = $relation_obj instanceof EE_Belongs_To_Relation ? null : array();
1000
-                }
1001
-                $entity_array[ Read::getRelatedEntityName($relation_name, $relation_obj) ] = $related_results;
1002
-            }
1003
-        }
1004
-        return $entity_array;
1005
-    }
1006
-
1007
-    /**
1008
-     * If the user has requested only specific properties (including meta properties like _links or _protected)
1009
-     * remove everything else.
1010
-     * @since 4.9.74.p
1011
-     * @param EEM_Base $model
1012
-     * @param WP_REST_Request $rest_request
1013
-     * @param $entity_array
1014
-     * @return array
1015
-     * @throws EE_Error
1016
-     */
1017
-    protected function includeOnlyRequestedProperties(
1018
-        EEM_Base $model,
1019
-        WP_REST_Request $rest_request,
1020
-        $entity_array
1021
-    ) {
1022
-
1023
-        $includes_for_this_model = $this->explodeAndGetItemsPrefixedWith($rest_request->get_param('include'), '');
1024
-        $includes_for_this_model = $this->removeModelNamesFromArray($includes_for_this_model);
1025
-        // if they passed in * or didn't specify any includes, return everything
1026
-        if (! in_array('*', $includes_for_this_model)
1027
-            && ! empty($includes_for_this_model)
1028
-        ) {
1029
-            if ($model->has_primary_key_field()) {
1030
-                // always include the primary key. ya just gotta know that at least
1031
-                $includes_for_this_model[] = $model->primary_key_name();
1032
-            }
1033
-            if ($this->explodeAndGetItemsPrefixedWith($rest_request->get_param('calculate'), '')) {
1034
-                $includes_for_this_model[] = '_calculated_fields';
1035
-            }
1036
-            $entity_array = array_intersect_key($entity_array, array_flip($includes_for_this_model));
1037
-        }
1038
-        return $entity_array;
1039
-    }
1040
-
1041
-
1042
-    /**
1043
-     * Returns a new array with all the names of models removed. Eg
1044
-     * array( 'Event', 'Datetime.*', 'foobar' ) would become array( 'Datetime.*', 'foobar' )
1045
-     *
1046
-     * @param array $arr
1047
-     * @return array
1048
-     */
1049
-    private function removeModelNamesFromArray($arr)
1050
-    {
1051
-        return array_diff($arr, array_keys(EE_Registry::instance()->non_abstract_db_models));
1052
-    }
1053
-
1054
-
1055
-    /**
1056
-     * Gets the calculated fields for the response
1057
-     *
1058
-     * @param EEM_Base        $model
1059
-     * @param array           $wpdb_row
1060
-     * @param WP_REST_Request $rest_request
1061
-     * @param boolean $row_is_protected whether this row is password protected or not
1062
-     * @return \stdClass the _calculations item in the entity
1063
-     * @throws ObjectDetectedException if a default value has a PHP object, which should never do (and if we
1064
-     * did, let's know about it ASAP, so let the exception bubble up)
1065
-     */
1066
-    protected function getEntityCalculations($model, $wpdb_row, $rest_request, $row_is_protected = false)
1067
-    {
1068
-        $calculated_fields = $this->explodeAndGetItemsPrefixedWith(
1069
-            $rest_request->get_param('calculate'),
1070
-            ''
1071
-        );
1072
-        // note: setting calculate=* doesn't do anything
1073
-        $calculated_fields_to_return = new \stdClass();
1074
-        $protected_fields = array();
1075
-        foreach ($calculated_fields as $field_to_calculate) {
1076
-            try {
1077
-                // it's password protected, so they shouldn't be able to read this. Remove the value
1078
-                $schema = $this->fields_calculator->getJsonSchemaForModel($model);
1079
-                if ($row_is_protected
1080
-                    && isset($schema['properties'][ $field_to_calculate ]['protected'])
1081
-                    && $schema['properties'][ $field_to_calculate ]['protected']) {
1082
-                    $calculated_value = null;
1083
-                    $protected_fields[] = $field_to_calculate;
1084
-                    if ($schema['properties'][ $field_to_calculate ]['type']) {
1085
-                        switch ($schema['properties'][ $field_to_calculate ]['type']) {
1086
-                            case 'boolean':
1087
-                                $calculated_value = false;
1088
-                                break;
1089
-                            case 'integer':
1090
-                                $calculated_value = 0;
1091
-                                break;
1092
-                            case 'string':
1093
-                                $calculated_value = '';
1094
-                                break;
1095
-                            case 'array':
1096
-                                $calculated_value = array();
1097
-                                break;
1098
-                            case 'object':
1099
-                                $calculated_value = new stdClass();
1100
-                                break;
1101
-                        }
1102
-                    }
1103
-                } else {
1104
-                    $calculated_value = ModelDataTranslator::prepareFieldValueForJson(
1105
-                        null,
1106
-                        $this->fields_calculator->retrieveCalculatedFieldValue(
1107
-                            $model,
1108
-                            $field_to_calculate,
1109
-                            $wpdb_row,
1110
-                            $rest_request,
1111
-                            $this
1112
-                        ),
1113
-                        $this->getModelVersionInfo()->requestedVersion()
1114
-                    );
1115
-                }
1116
-                $calculated_fields_to_return->{$field_to_calculate} = $calculated_value;
1117
-            } catch (RestException $e) {
1118
-                // if we don't have permission to read it, just leave it out. but let devs know about the problem
1119
-                $this->setResponseHeader(
1120
-                    'Notices-Field-Calculation-Errors['
1121
-                    . $e->getStringCode()
1122
-                    . ']['
1123
-                    . $model->get_this_model_name()
1124
-                    . ']['
1125
-                    . $field_to_calculate
1126
-                    . ']',
1127
-                    $e->getMessage(),
1128
-                    true
1129
-                );
1130
-            }
1131
-        }
1132
-        $calculated_fields_to_return->_protected = $protected_fields;
1133
-        return $calculated_fields_to_return;
1134
-    }
1135
-
1136
-
1137
-    /**
1138
-     * Gets the full URL to the resource, taking the requested version into account
1139
-     *
1140
-     * @param string $link_part_after_version_and_slash eg "events/10/datetimes"
1141
-     * @return string url eg "http://mysite.com/wp-json/ee/v4.6/events/10/datetimes"
1142
-     */
1143
-    public function getVersionedLinkTo($link_part_after_version_and_slash)
1144
-    {
1145
-        return rest_url(
1146
-            EED_Core_Rest_Api::get_versioned_route_to(
1147
-                $link_part_after_version_and_slash,
1148
-                $this->getModelVersionInfo()->requestedVersion()
1149
-            )
1150
-        );
1151
-    }
1152
-
1153
-
1154
-    /**
1155
-     * Gets the correct lowercase name for the relation in the API according
1156
-     * to the relation's type
1157
-     *
1158
-     * @param string                  $relation_name
1159
-     * @param \EE_Model_Relation_Base $relation_obj
1160
-     * @return string
1161
-     */
1162
-    public static function getRelatedEntityName($relation_name, $relation_obj)
1163
-    {
1164
-        if ($relation_obj instanceof EE_Belongs_To_Relation) {
1165
-            return strtolower($relation_name);
1166
-        } else {
1167
-            return EEH_Inflector::pluralize_and_lower($relation_name);
1168
-        }
1169
-    }
1170
-
1171
-
1172
-    /**
1173
-     * Gets the one model object with the specified id for the specified model
1174
-     *
1175
-     * @param EEM_Base        $model
1176
-     * @param WP_REST_Request $request
1177
-     * @return array
1178
-     */
1179
-    public function getEntityFromModel($model, $request)
1180
-    {
1181
-        $context = $this->validateContext($request->get_param('caps'));
1182
-        return $this->getOneOrReportPermissionError($model, $request, $context);
1183
-    }
1184
-
1185
-
1186
-    /**
1187
-     * If a context is provided which isn't valid, maybe it was added in a future
1188
-     * version so just treat it as a default read
1189
-     *
1190
-     * @param string $context
1191
-     * @return string array key of EEM_Base::cap_contexts_to_cap_action_map()
1192
-     */
1193
-    public function validateContext($context)
1194
-    {
1195
-        if (! $context) {
1196
-            $context = EEM_Base::caps_read;
1197
-        }
1198
-        $valid_contexts = EEM_Base::valid_cap_contexts();
1199
-        if (in_array($context, $valid_contexts)) {
1200
-            return $context;
1201
-        } else {
1202
-            return EEM_Base::caps_read;
1203
-        }
1204
-    }
1205
-
1206
-
1207
-    /**
1208
-     * Verifies the passed in value is an allowable default where conditions value.
1209
-     *
1210
-     * @param $default_query_params
1211
-     * @return string
1212
-     */
1213
-    public function validateDefaultQueryParams($default_query_params)
1214
-    {
1215
-        $valid_default_where_conditions_for_api_calls = array(
1216
-            EEM_Base::default_where_conditions_all,
1217
-            EEM_Base::default_where_conditions_minimum_all,
1218
-            EEM_Base::default_where_conditions_minimum_others,
1219
-        );
1220
-        if (! $default_query_params) {
1221
-            $default_query_params = EEM_Base::default_where_conditions_all;
1222
-        }
1223
-        if (in_array(
1224
-            $default_query_params,
1225
-            $valid_default_where_conditions_for_api_calls,
1226
-            true
1227
-        )) {
1228
-            return $default_query_params;
1229
-        } else {
1230
-            return EEM_Base::default_where_conditions_all;
1231
-        }
1232
-    }
1233
-
1234
-
1235
-    /**
1236
-     * Translates API filter get parameter into model query params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions.
1237
-     * Note: right now the query parameter keys for fields (and related fields)
1238
-     * can be left as-is, but it's quite possible this will change someday.
1239
-     * Also, this method's contents might be candidate for moving to Model_Data_Translator
1240
-     *
1241
-     * @param EEM_Base $model
1242
-     * @param array    $query_parameters  from $_GET parameter @see Read:handle_request_get_all
1243
-     * @return array model query params (@see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions)
1244
-     *                                    or FALSE to indicate that absolutely no results should be returned
1245
-     * @throws EE_Error
1246
-     * @throws RestException
1247
-     */
1248
-    public function createModelQueryParams($model, $query_params)
1249
-    {
1250
-        $model_query_params = array();
1251
-        if (isset($query_params['where'])) {
1252
-            $model_query_params[0] = ModelDataTranslator::prepareConditionsQueryParamsForModels(
1253
-                $query_params['where'],
1254
-                $model,
1255
-                $this->getModelVersionInfo()->requestedVersion()
1256
-            );
1257
-        }
1258
-        if (isset($query_params['order_by'])) {
1259
-            $order_by = $query_params['order_by'];
1260
-        } elseif (isset($query_params['orderby'])) {
1261
-            $order_by = $query_params['orderby'];
1262
-        } else {
1263
-            $order_by = null;
1264
-        }
1265
-        if ($order_by !== null) {
1266
-            if (is_array($order_by)) {
1267
-                $order_by = ModelDataTranslator::prepareFieldNamesInArrayKeysFromJson($order_by);
1268
-            } else {
1269
-                // it's a single item
1270
-                $order_by = ModelDataTranslator::prepareFieldNameFromJson($order_by);
1271
-            }
1272
-            $model_query_params['order_by'] = $order_by;
1273
-        }
1274
-        if (isset($query_params['group_by'])) {
1275
-            $group_by = $query_params['group_by'];
1276
-        } elseif (isset($query_params['groupby'])) {
1277
-            $group_by = $query_params['groupby'];
1278
-        } else {
1279
-            $group_by = array_keys($model->get_combined_primary_key_fields());
1280
-        }
1281
-        // make sure they're all real names
1282
-        if (is_array($group_by)) {
1283
-            $group_by = ModelDataTranslator::prepareFieldNamesFromJson($group_by);
1284
-        }
1285
-        if ($group_by !== null) {
1286
-            $model_query_params['group_by'] = $group_by;
1287
-        }
1288
-        if (isset($query_params['having'])) {
1289
-            $model_query_params['having'] = ModelDataTranslator::prepareConditionsQueryParamsForModels(
1290
-                $query_params['having'],
1291
-                $model,
1292
-                $this->getModelVersionInfo()->requestedVersion()
1293
-            );
1294
-        }
1295
-        if (isset($query_params['order'])) {
1296
-            $model_query_params['order'] = $query_params['order'];
1297
-        }
1298
-        if (isset($query_params['mine'])) {
1299
-            $model_query_params = $model->alter_query_params_to_only_include_mine($model_query_params);
1300
-        }
1301
-        if (isset($query_params['limit'])) {
1302
-            // limit should be either a string like '23' or '23,43', or an array with two items in it
1303
-            if (! is_array($query_params['limit'])) {
1304
-                $limit_array = explode(',', (string) $query_params['limit']);
1305
-            } else {
1306
-                $limit_array = $query_params['limit'];
1307
-            }
1308
-            $sanitized_limit = array();
1309
-            foreach ($limit_array as $key => $limit_part) {
1310
-                if ($this->debug_mode && (! is_numeric($limit_part) || count($sanitized_limit) > 2)) {
1311
-                    throw new EE_Error(
1312
-                        sprintf(
1313
-                            __(
1314
-                            // @codingStandardsIgnoreStart
1315
-                                '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.',
1316
-                                // @codingStandardsIgnoreEnd
1317
-                                'event_espresso'
1318
-                            ),
1319
-                            wp_json_encode($query_params['limit'])
1320
-                        )
1321
-                    );
1322
-                }
1323
-                $sanitized_limit[] = (int) $limit_part;
1324
-            }
1325
-            $model_query_params['limit'] = implode(',', $sanitized_limit);
1326
-        } else {
1327
-            $model_query_params['limit'] = EED_Core_Rest_Api::get_default_query_limit();
1328
-        }
1329
-        if (isset($query_params['caps'])) {
1330
-            $model_query_params['caps'] = $this->validateContext($query_params['caps']);
1331
-        } else {
1332
-            $model_query_params['caps'] = EEM_Base::caps_read;
1333
-        }
1334
-        if (isset($query_params['default_where_conditions'])) {
1335
-            $model_query_params['default_where_conditions'] = $this->validateDefaultQueryParams(
1336
-                $query_params['default_where_conditions']
1337
-            );
1338
-        }
1339
-        // if this is a model protected by a password on another model, exclude the password protected
1340
-        // entities by default. But if they passed in a password, try to show them all. If the password is wrong,
1341
-        // though, they'll get an error (see Read::createEntityFromWpdbResult() which calls Read::checkPassword)
1342
-        if (! $model->hasPassword()
1343
-            && $model->restrictedByRelatedModelPassword()
1344
-            && $model_query_params['caps'] === EEM_Base::caps_read) {
1345
-            if (empty($query_params['password'])) {
1346
-                $model_query_params['exclude_protected'] = true;
1347
-            }
1348
-        }
1349
-
1350
-        return apply_filters('FHEE__Read__create_model_query_params', $model_query_params, $query_params, $model);
1351
-    }
1352
-
1353
-
1354
-    /**
1355
-     * Changes the REST-style query params for use in the models
1356
-     *
1357
-     * @deprecated
1358
-     * @param EEM_Base $model
1359
-     * @param array    $query_params sub-array from @see EEM_Base::get_all()
1360
-     * @return array
1361
-     */
1362
-    public function prepareRestQueryParamsKeyForModels($model, $query_params)
1363
-    {
1364
-        $model_ready_query_params = array();
1365
-        foreach ($query_params as $key => $value) {
1366
-            if (is_array($value)) {
1367
-                $model_ready_query_params[ $key ] = $this->prepareRestQueryParamsKeyForModels($model, $value);
1368
-            } else {
1369
-                $model_ready_query_params[ $key ] = $value;
1370
-            }
1371
-        }
1372
-        return $model_ready_query_params;
1373
-    }
1374
-
1375
-
1376
-    /**
1377
-     * @deprecated instead use ModelDataTranslator::prepareFieldValuesFromJson()
1378
-     * @param $model
1379
-     * @param $query_params
1380
-     * @return array
1381
-     */
1382
-    public function prepareRestQueryParamsValuesForModels($model, $query_params)
1383
-    {
1384
-        $model_ready_query_params = array();
1385
-        foreach ($query_params as $key => $value) {
1386
-            if (is_array($value)) {
1387
-                $model_ready_query_params[ $key ] = $this->prepareRestQueryParamsValuesForModels($model, $value);
1388
-            } else {
1389
-                $model_ready_query_params[ $key ] = $value;
1390
-            }
1391
-        }
1392
-        return $model_ready_query_params;
1393
-    }
1394
-
1395
-
1396
-    /**
1397
-     * Explodes the string on commas, and only returns items with $prefix followed by a period.
1398
-     * If no prefix is specified, returns items with no period.
1399
-     *
1400
-     * @param string|array $string_to_explode eg "jibba,jabba, blah, blah, blah" or array('jibba', 'jabba' )
1401
-     * @param string       $prefix            "Event" or "foobar"
1402
-     * @return array $string_to_exploded exploded on COMMAS, and if a prefix was specified
1403
-     *                                        we only return strings starting with that and a period; if no prefix was
1404
-     *                                        specified we return all items containing NO periods
1405
-     */
1406
-    public function explodeAndGetItemsPrefixedWith($string_to_explode, $prefix)
1407
-    {
1408
-        if (is_string($string_to_explode)) {
1409
-            $exploded_contents = explode(',', $string_to_explode);
1410
-        } elseif (is_array($string_to_explode)) {
1411
-            $exploded_contents = $string_to_explode;
1412
-        } else {
1413
-            $exploded_contents = array();
1414
-        }
1415
-        // if the string was empty, we want an empty array
1416
-        $exploded_contents = array_filter($exploded_contents);
1417
-        $contents_with_prefix = array();
1418
-        foreach ($exploded_contents as $item) {
1419
-            $item = trim($item);
1420
-            // if no prefix was provided, so we look for items with no "." in them
1421
-            if (! $prefix) {
1422
-                // does this item have a period?
1423
-                if (strpos($item, '.') === false) {
1424
-                    // if not, then its what we're looking for
1425
-                    $contents_with_prefix[] = $item;
1426
-                }
1427
-            } elseif (strpos($item, $prefix . '.') === 0) {
1428
-                // this item has the prefix and a period, grab it
1429
-                $contents_with_prefix[] = substr(
1430
-                    $item,
1431
-                    strpos($item, $prefix . '.') + strlen($prefix . '.')
1432
-                );
1433
-            } elseif ($item === $prefix) {
1434
-                // this item is JUST the prefix
1435
-                // so let's grab everything after, which is a blank string
1436
-                $contents_with_prefix[] = '';
1437
-            }
1438
-        }
1439
-        return $contents_with_prefix;
1440
-    }
1441
-
1442
-
1443
-    /**
1444
-     * @deprecated since 4.8.36.rc.001 You should instead use Read::explode_and_get_items_prefixed_with.
1445
-     * Deprecated because its return values were really quite confusing- sometimes it returned
1446
-     * an empty array (when the include string was blank or '*') or sometimes it returned
1447
-     * array('*') (when you provided a model and a model of that kind was found).
1448
-     * Parses the $include_string so we fetch all the field names relating to THIS model
1449
-     * (ie have NO period in them), or for the provided model (ie start with the model
1450
-     * name and then a period).
1451
-     * @param string $include_string @see Read:handle_request_get_all
1452
-     * @param string $model_name
1453
-     * @return array of fields for this model. If $model_name is provided, then
1454
-     *                               the fields for that model, with the model's name removed from each.
1455
-     *                               If $include_string was blank or '*' returns an empty array
1456
-     */
1457
-    public function extractIncludesForThisModel($include_string, $model_name = null)
1458
-    {
1459
-        if (is_array($include_string)) {
1460
-            $include_string = implode(',', $include_string);
1461
-        }
1462
-        if ($include_string === '*' || $include_string === '') {
1463
-            return array();
1464
-        }
1465
-        $includes = explode(',', $include_string);
1466
-        $extracted_fields_to_include = array();
1467
-        if ($model_name) {
1468
-            foreach ($includes as $field_to_include) {
1469
-                $field_to_include = trim($field_to_include);
1470
-                if (strpos($field_to_include, $model_name . '.') === 0) {
1471
-                    // found the model name at the exact start
1472
-                    $field_sans_model_name = str_replace($model_name . '.', '', $field_to_include);
1473
-                    $extracted_fields_to_include[] = $field_sans_model_name;
1474
-                } elseif ($field_to_include == $model_name) {
1475
-                    $extracted_fields_to_include[] = '*';
1476
-                }
1477
-            }
1478
-        } else {
1479
-            // look for ones with no period
1480
-            foreach ($includes as $field_to_include) {
1481
-                $field_to_include = trim($field_to_include);
1482
-                if (strpos($field_to_include, '.') === false
1483
-                    && ! $this->getModelVersionInfo()->isModelNameInThisVersion($field_to_include)
1484
-                ) {
1485
-                    $extracted_fields_to_include[] = $field_to_include;
1486
-                }
1487
-            }
1488
-        }
1489
-        return $extracted_fields_to_include;
1490
-    }
1491
-
1492
-
1493
-    /**
1494
-     * Gets the single item using the model according to the request in the context given, otherwise
1495
-     * returns that it's inaccessible to the current user
1496
-     *
1497
-     * @param EEM_Base $model
1498
-     * @param WP_REST_Request $request
1499
-     * @param null $context
1500
-     * @return array
1501
-     * @throws EE_Error
1502
-     */
1503
-    public function getOneOrReportPermissionError(EEM_Base $model, WP_REST_Request $request, $context = null)
1504
-    {
1505
-        $query_params = array(array($model->primary_key_name() => $request->get_param('id')), 'limit' => 1);
1506
-        if ($model instanceof EEM_Soft_Delete_Base) {
1507
-            $query_params = $model->alter_query_params_so_deleted_and_undeleted_items_included($query_params);
1508
-        }
1509
-        $restricted_query_params = $query_params;
1510
-        $restricted_query_params['caps'] = $context;
1511
-        $this->setDebugInfo('model query params', $restricted_query_params);
1512
-        $model_rows = $model->get_all_wpdb_results($restricted_query_params);
1513
-        if (! empty($model_rows)) {
1514
-            return $this->createEntityFromWpdbResult(
1515
-                $model,
1516
-                reset($model_rows),
1517
-                $request
1518
-            );
1519
-        } else {
1520
-            // ok let's test to see if we WOULD have found it, had we not had restrictions from missing capabilities
1521
-            $lowercase_model_name = strtolower($model->get_this_model_name());
1522
-            if ($model->exists($query_params)) {
1523
-                // you got shafted- it existed but we didn't want to tell you!
1524
-                throw new RestException(
1525
-                    'rest_user_cannot_' . $context,
1526
-                    sprintf(
1527
-                        __('Sorry, you cannot %1$s this %2$s. Missing permissions are: %3$s', 'event_espresso'),
1528
-                        $context,
1529
-                        $lowercase_model_name,
1530
-                        Capabilities::getMissingPermissionsString(
1531
-                            $model,
1532
-                            $context
1533
-                        )
1534
-                    ),
1535
-                    array('status' => 403)
1536
-                );
1537
-            } else {
1538
-                // it's not you. It just doesn't exist
1539
-                throw new RestException(
1540
-                    sprintf('rest_%s_invalid_id', $lowercase_model_name),
1541
-                    sprintf(__('Invalid %s ID.', 'event_espresso'), $lowercase_model_name),
1542
-                    array('status' => 404)
1543
-                );
1544
-            }
1545
-        }
1546
-    }
1547
-
1548
-    /**
1549
-     * Checks that if this content requires a password to be read, that it's been provided and is correct.
1550
-     * @since 4.9.74.p
1551
-     * @param EEM_Base $model
1552
-     * @param $model_row
1553
-     * @param $query_params Adds 'default_where_conditions' => 'minimum' to ensure we don't confuse trashed with
1554
-     *                      password protected.
1555
-     * @param WP_REST_Request $request
1556
-     * @throws EE_Error
1557
-     * @throws InvalidArgumentException
1558
-     * @throws InvalidDataTypeException
1559
-     * @throws InvalidInterfaceException
1560
-     * @throws RestPasswordRequiredException
1561
-     * @throws RestPasswordIncorrectException
1562
-     * @throws \EventEspresso\core\exceptions\ModelConfigurationException
1563
-     * @throws ReflectionException
1564
-     */
1565
-    protected function checkPassword(EEM_Base $model, $model_row, $query_params, WP_REST_Request $request)
1566
-    {
1567
-        $query_params['default_where_conditions'] = 'minimum';
1568
-        // stuff is only "protected" for front-end requests. Elsewhere, you either get full permission to access the object
1569
-        // or you don't.
1570
-        $request_caps = $request->get_param('caps');
1571
-        if (isset($request_caps) && $request_caps !== EEM_Base::caps_read) {
1572
-            return;
1573
-        }
1574
-        // if this entity requires a password, they better give it and it better be right!
1575
-        if ($model->hasPassword()
1576
-            && $model_row[ $model->getPasswordField()->get_qualified_column() ] !== '') {
1577
-            if (empty($request['password'])) {
1578
-                throw new RestPasswordRequiredException();
1579
-            } elseif (!hash_equals(
1580
-                $model_row[ $model->getPasswordField()->get_qualified_column() ],
1581
-                $request['password']
1582
-            )) {
1583
-                throw new RestPasswordIncorrectException();
1584
-            }
1585
-        } // wait! maybe this content is password protected
1586
-        elseif ($model->restrictedByRelatedModelPassword()
1587
-            && $request->get_param('caps') === EEM_Base::caps_read) {
1588
-            $password_supplied = $request->get_param('password');
1589
-            if (empty($password_supplied)) {
1590
-                $query_params['exclude_protected'] = true;
1591
-                if (!$model->exists($query_params)) {
1592
-                    throw new RestPasswordRequiredException();
1593
-                }
1594
-            } else {
1595
-                $query_params[0][ $model->modelChainAndPassword() ] = $password_supplied;
1596
-                if (!$model->exists($query_params)) {
1597
-                    throw new RestPasswordIncorrectException();
1598
-                }
1599
-            }
1600
-        }
1601
-    }
48
+	/**
49
+	 * @var CalculatedModelFields
50
+	 */
51
+	protected $fields_calculator;
52
+
53
+
54
+	/**
55
+	 * Read constructor.
56
+	 * @param CalculatedModelFields $fields_calculator
57
+	 */
58
+	public function __construct(CalculatedModelFields $fields_calculator)
59
+	{
60
+		parent::__construct();
61
+		$this->fields_calculator = $fields_calculator;
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|WP_Error
72
+	 * @throws InvalidArgumentException
73
+	 * @throws InvalidDataTypeException
74
+	 * @throws InvalidInterfaceException
75
+	 */
76
+	public static function handleRequestGetAll(WP_REST_Request $request, $version, $model_name)
77
+	{
78
+		$controller = LoaderFactory::getLoader()->getNew('EventEspresso\core\libraries\rest_api\controllers\model\Read');
79
+		try {
80
+			$controller->setRequestedVersion($version);
81
+			if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
82
+				return $controller->sendResponse(
83
+					new WP_Error(
84
+						'endpoint_parsing_error',
85
+						sprintf(
86
+							__(
87
+								'There is no model for endpoint %s. Please contact event espresso support',
88
+								'event_espresso'
89
+							),
90
+							$model_name
91
+						)
92
+					)
93
+				);
94
+			}
95
+			return $controller->sendResponse(
96
+				$controller->getEntitiesFromModel(
97
+					$controller->getModelVersionInfo()->loadModel($model_name),
98
+					$request
99
+				)
100
+			);
101
+		} catch (Exception $e) {
102
+			return $controller->sendResponse($e);
103
+		}
104
+	}
105
+
106
+
107
+	/**
108
+	 * Prepares and returns schema for any OPTIONS request.
109
+	 *
110
+	 * @param string $version The API endpoint version being used.
111
+	 * @param string $model_name Something like `Event` or `Registration`
112
+	 * @return array
113
+	 * @throws InvalidArgumentException
114
+	 * @throws InvalidDataTypeException
115
+	 * @throws InvalidInterfaceException
116
+	 */
117
+	public static function handleSchemaRequest($version, $model_name)
118
+	{
119
+		$controller = LoaderFactory::getLoader()->getNew('EventEspresso\core\libraries\rest_api\controllers\model\Read');
120
+		try {
121
+			$controller->setRequestedVersion($version);
122
+			if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
123
+				return array();
124
+			}
125
+			// get the model for this version
126
+			$model = $controller->getModelVersionInfo()->loadModel($model_name);
127
+			$model_schema = new JsonModelSchema($model, LoaderFactory::getLoader()->getShared('EventEspresso\core\libraries\rest_api\CalculatedModelFields'));
128
+			return $model_schema->getModelSchemaForRelations(
129
+				$controller->getModelVersionInfo()->relationSettings($model),
130
+				$controller->customizeSchemaForRestResponse(
131
+					$model,
132
+					$model_schema->getModelSchemaForFields(
133
+						$controller->getModelVersionInfo()->fieldsOnModelInThisVersion($model),
134
+						$model_schema->getInitialSchemaStructure()
135
+					)
136
+				)
137
+			);
138
+		} catch (Exception $e) {
139
+			return array();
140
+		}
141
+	}
142
+
143
+
144
+	/**
145
+	 * This loops through each field in the given schema for the model and does the following:
146
+	 * - add any extra fields that are REST API specific and related to existing fields.
147
+	 * - transform default values into the correct format for a REST API response.
148
+	 *
149
+	 * @param EEM_Base $model
150
+	 * @param array    $schema
151
+	 * @return array  The final schema.
152
+	 */
153
+	protected function customizeSchemaForRestResponse(EEM_Base $model, array $schema)
154
+	{
155
+		foreach ($this->getModelVersionInfo()->fieldsOnModelInThisVersion($model) as $field_name => $field) {
156
+			$schema = $this->translateDefaultsForRestResponse(
157
+				$field_name,
158
+				$field,
159
+				$this->maybeAddExtraFieldsToSchema($field_name, $field, $schema)
160
+			);
161
+		}
162
+		return $schema;
163
+	}
164
+
165
+
166
+	/**
167
+	 * This is used to ensure that the 'default' value set in the schema response is formatted correctly for the REST
168
+	 * response.
169
+	 *
170
+	 * @param                      $field_name
171
+	 * @param EE_Model_Field_Base  $field
172
+	 * @param array                $schema
173
+	 * @return array
174
+	 * @throws ObjectDetectedException if a default value has a PHP object, which should never do (and if we
175
+	 * did, let's know about it ASAP, so let the exception bubble up)
176
+	 */
177
+	protected function translateDefaultsForRestResponse($field_name, EE_Model_Field_Base $field, array $schema)
178
+	{
179
+		if (isset($schema['properties'][ $field_name ]['default'])) {
180
+			if (is_array($schema['properties'][ $field_name ]['default'])) {
181
+				foreach ($schema['properties'][ $field_name ]['default'] as $default_key => $default_value) {
182
+					if ($default_key === 'raw') {
183
+						$schema['properties'][ $field_name ]['default'][ $default_key ] =
184
+							ModelDataTranslator::prepareFieldValueForJson(
185
+								$field,
186
+								$default_value,
187
+								$this->getModelVersionInfo()->requestedVersion()
188
+							);
189
+					}
190
+				}
191
+			} else {
192
+				$schema['properties'][ $field_name ]['default'] = ModelDataTranslator::prepareFieldValueForJson(
193
+					$field,
194
+					$schema['properties'][ $field_name ]['default'],
195
+					$this->getModelVersionInfo()->requestedVersion()
196
+				);
197
+			}
198
+		}
199
+		return $schema;
200
+	}
201
+
202
+
203
+	/**
204
+	 * Adds additional fields to the schema
205
+	 * The REST API returns a GMT value field for each datetime field in the resource.  Thus the description about this
206
+	 * needs to be added to the schema.
207
+	 *
208
+	 * @param                      $field_name
209
+	 * @param EE_Model_Field_Base  $field
210
+	 * @param array                $schema
211
+	 * @return array
212
+	 */
213
+	protected function maybeAddExtraFieldsToSchema($field_name, EE_Model_Field_Base $field, array $schema)
214
+	{
215
+		if ($field instanceof EE_Datetime_Field) {
216
+			$schema['properties'][ $field_name . '_gmt' ] = $field->getSchema();
217
+			// modify the description
218
+			$schema['properties'][ $field_name . '_gmt' ]['description'] = sprintf(
219
+				esc_html__('%s - the value for this field is in GMT.', 'event_espresso'),
220
+				wp_specialchars_decode($field->get_nicename(), ENT_QUOTES)
221
+			);
222
+		}
223
+		return $schema;
224
+	}
225
+
226
+
227
+	/**
228
+	 * Used to figure out the route from the request when a `WP_REST_Request` object is not available
229
+	 *
230
+	 * @return string
231
+	 */
232
+	protected function getRouteFromRequest()
233
+	{
234
+		if (isset($GLOBALS['wp'])
235
+			&& $GLOBALS['wp'] instanceof \WP
236
+			&& isset($GLOBALS['wp']->query_vars['rest_route'])
237
+		) {
238
+			return $GLOBALS['wp']->query_vars['rest_route'];
239
+		} else {
240
+			return isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : '/';
241
+		}
242
+	}
243
+
244
+
245
+	/**
246
+	 * Gets a single entity related to the model indicated in the path and its id
247
+	 *
248
+	 * @param WP_REST_Request $request
249
+	 * @param string $version
250
+	 * @param string $model_name
251
+	 * @return WP_REST_Response|WP_Error
252
+	 * @throws InvalidDataTypeException
253
+	 * @throws InvalidInterfaceException
254
+	 * @throws InvalidArgumentException
255
+	 */
256
+	public static function handleRequestGetOne(WP_REST_Request $request, $version, $model_name)
257
+	{
258
+		$controller = LoaderFactory::getLoader()->getNew('EventEspresso\core\libraries\rest_api\controllers\model\Read');
259
+		try {
260
+			$controller->setRequestedVersion($version);
261
+			if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
262
+				return $controller->sendResponse(
263
+					new WP_Error(
264
+						'endpoint_parsing_error',
265
+						sprintf(
266
+							__(
267
+								'There is no model for endpoint %s. Please contact event espresso support',
268
+								'event_espresso'
269
+							),
270
+							$model_name
271
+						)
272
+					)
273
+				);
274
+			}
275
+			return $controller->sendResponse(
276
+				$controller->getEntityFromModel(
277
+					$controller->getModelVersionInfo()->loadModel($model_name),
278
+					$request
279
+				)
280
+			);
281
+		} catch (Exception $e) {
282
+			return $controller->sendResponse($e);
283
+		}
284
+	}
285
+
286
+
287
+	/**
288
+	 * Gets all the related entities (or if its a belongs-to relation just the one)
289
+	 * to the item with the given id
290
+	 *
291
+	 * @param WP_REST_Request $request
292
+	 * @param string $version
293
+	 * @param string $model_name
294
+	 * @param string $related_model_name
295
+	 * @return WP_REST_Response|WP_Error
296
+	 * @throws InvalidDataTypeException
297
+	 * @throws InvalidInterfaceException
298
+	 * @throws InvalidArgumentException
299
+	 */
300
+	public static function handleRequestGetRelated(
301
+		WP_REST_Request $request,
302
+		$version,
303
+		$model_name,
304
+		$related_model_name
305
+	) {
306
+		$controller = LoaderFactory::getLoader()->getNew('EventEspresso\core\libraries\rest_api\controllers\model\Read');
307
+		try {
308
+			$controller->setRequestedVersion($version);
309
+			$main_model = $controller->validateModel($model_name);
310
+			$controller->validateModel($related_model_name);
311
+			return $controller->sendResponse(
312
+				$controller->getEntitiesFromRelation(
313
+					$request->get_param('id'),
314
+					$main_model->related_settings_for($related_model_name),
315
+					$request
316
+				)
317
+			);
318
+		} catch (Exception $e) {
319
+			return $controller->sendResponse($e);
320
+		}
321
+	}
322
+
323
+
324
+	/**
325
+	 * Gets a collection for the given model and filters
326
+	 *
327
+	 * @param EEM_Base $model
328
+	 * @param WP_REST_Request $request
329
+	 * @return array
330
+	 * @throws EE_Error
331
+	 * @throws InvalidArgumentException
332
+	 * @throws InvalidDataTypeException
333
+	 * @throws InvalidInterfaceException
334
+	 * @throws ReflectionException
335
+	 * @throws RestException
336
+	 */
337
+	public function getEntitiesFromModel($model, $request)
338
+	{
339
+		$query_params = $this->createModelQueryParams($model, $request->get_params());
340
+		if (! Capabilities::currentUserHasPartialAccessTo($model, $query_params['caps'])) {
341
+			$model_name_plural = EEH_Inflector::pluralize_and_lower($model->get_this_model_name());
342
+			throw new RestException(
343
+				sprintf('rest_%s_cannot_list', $model_name_plural),
344
+				sprintf(
345
+					__('Sorry, you are not allowed to list %1$s. Missing permissions: %2$s', 'event_espresso'),
346
+					$model_name_plural,
347
+					Capabilities::getMissingPermissionsString($model, $query_params['caps'])
348
+				),
349
+				array('status' => 403)
350
+			);
351
+		}
352
+		if (! $request->get_header('no_rest_headers')) {
353
+			$this->setHeadersFromQueryParams($model, $query_params);
354
+		}
355
+		/** @type array $results */
356
+		$results = $model->get_all_wpdb_results($query_params);
357
+		$nice_results = array();
358
+		foreach ($results as $result) {
359
+			$nice_results[] =  $this->createEntityFromWpdbResult(
360
+				$model,
361
+				$result,
362
+				$request
363
+			);
364
+		}
365
+		return $nice_results;
366
+	}
367
+
368
+
369
+	/**
370
+	 * Gets the collection for given relation object
371
+	 * The same as Read::get_entities_from_model(), except if the relation
372
+	 * is a HABTM relation, in which case it merges any non-foreign-key fields from
373
+	 * the join-model-object into the results
374
+	 *
375
+	 * @param array $primary_model_query_params query params for finding the item from which
376
+	 *                                                            relations will be based
377
+	 * @param \EE_Model_Relation_Base $relation
378
+	 * @param WP_REST_Request $request
379
+	 * @return array
380
+	 * @throws EE_Error
381
+	 * @throws InvalidArgumentException
382
+	 * @throws InvalidDataTypeException
383
+	 * @throws InvalidInterfaceException
384
+	 * @throws ReflectionException
385
+	 * @throws RestException
386
+	 * @throws \EventEspresso\core\exceptions\ModelConfigurationException
387
+	 */
388
+	protected function getEntitiesFromRelationUsingModelQueryParams($primary_model_query_params, $relation, $request)
389
+	{
390
+		$context = $this->validateContext($request->get_param('caps'));
391
+		$model = $relation->get_this_model();
392
+		$related_model = $relation->get_other_model();
393
+		if (! isset($primary_model_query_params[0])) {
394
+			$primary_model_query_params[0] = array();
395
+		}
396
+		// check if they can access the 1st model object
397
+		$primary_model_query_params = array(
398
+			0       => $primary_model_query_params[0],
399
+			'limit' => 1,
400
+		);
401
+		if ($model instanceof EEM_Soft_Delete_Base) {
402
+			$primary_model_query_params = $model->alter_query_params_so_deleted_and_undeleted_items_included(
403
+				$primary_model_query_params
404
+			);
405
+		}
406
+		$restricted_query_params = $primary_model_query_params;
407
+		$restricted_query_params['caps'] = $context;
408
+		$restricted_query_params['limit'] = 1;
409
+		$this->setDebugInfo('main model query params', $restricted_query_params);
410
+		$this->setDebugInfo('missing caps', Capabilities::getMissingPermissionsString($related_model, $context));
411
+		$primary_model_rows = $model->get_all_wpdb_results($restricted_query_params);
412
+		$primary_model_row = null;
413
+		if (is_array($primary_model_rows)) {
414
+			$primary_model_row = reset($primary_model_rows);
415
+		}
416
+		if (! (
417
+			Capabilities::currentUserHasPartialAccessTo($related_model, $context)
418
+			&& $primary_model_row
419
+		)
420
+		) {
421
+			if ($relation instanceof EE_Belongs_To_Relation) {
422
+				$related_model_name_maybe_plural = strtolower($related_model->get_this_model_name());
423
+			} else {
424
+				$related_model_name_maybe_plural = EEH_Inflector::pluralize_and_lower(
425
+					$related_model->get_this_model_name()
426
+				);
427
+			}
428
+			throw new RestException(
429
+				sprintf('rest_%s_cannot_list', $related_model_name_maybe_plural),
430
+				sprintf(
431
+					__(
432
+						'Sorry, you are not allowed to list %1$s related to %2$s. Missing permissions: %3$s',
433
+						'event_espresso'
434
+					),
435
+					$related_model_name_maybe_plural,
436
+					$relation->get_this_model()->get_this_model_name(),
437
+					implode(
438
+						',',
439
+						array_keys(
440
+							Capabilities::getMissingPermissions($related_model, $context)
441
+						)
442
+					)
443
+				),
444
+				array('status' => 403)
445
+			);
446
+		}
447
+
448
+		$this->checkPassword(
449
+			$model,
450
+			$primary_model_row,
451
+			$restricted_query_params,
452
+			$request
453
+		);
454
+		$query_params = $this->createModelQueryParams($relation->get_other_model(), $request->get_params());
455
+		foreach ($primary_model_query_params[0] as $where_condition_key => $where_condition_value) {
456
+			$query_params[0][ $relation->get_this_model()->get_this_model_name()
457
+							  . '.'
458
+							  . $where_condition_key ] = $where_condition_value;
459
+		}
460
+		$query_params['default_where_conditions'] = 'none';
461
+		$query_params['caps'] = $context;
462
+		if (! $request->get_header('no_rest_headers')) {
463
+			$this->setHeadersFromQueryParams($relation->get_other_model(), $query_params);
464
+		}
465
+		/** @type array $results */
466
+		$results = $relation->get_other_model()->get_all_wpdb_results($query_params);
467
+		$nice_results = array();
468
+		foreach ($results as $result) {
469
+			$nice_result = $this->createEntityFromWpdbResult(
470
+				$relation->get_other_model(),
471
+				$result,
472
+				$request
473
+			);
474
+			if ($relation instanceof \EE_HABTM_Relation) {
475
+				// put the unusual stuff (properties from the HABTM relation) first, and make sure
476
+				// if there are conflicts we prefer the properties from the main model
477
+				$join_model_result = $this->createEntityFromWpdbResult(
478
+					$relation->get_join_model(),
479
+					$result,
480
+					$request
481
+				);
482
+				$joined_result = array_merge($nice_result, $join_model_result);
483
+				// but keep the meta stuff from the main model
484
+				if (isset($nice_result['meta'])) {
485
+					$joined_result['meta'] = $nice_result['meta'];
486
+				}
487
+				$nice_result = $joined_result;
488
+			}
489
+			$nice_results[] = $nice_result;
490
+		}
491
+		if ($relation instanceof EE_Belongs_To_Relation) {
492
+			return array_shift($nice_results);
493
+		} else {
494
+			return $nice_results;
495
+		}
496
+	}
497
+
498
+
499
+	/**
500
+	 * Gets the collection for given relation object
501
+	 * The same as Read::get_entities_from_model(), except if the relation
502
+	 * is a HABTM relation, in which case it merges any non-foreign-key fields from
503
+	 * the join-model-object into the results
504
+	 *
505
+	 * @param string                  $id the ID of the thing we are fetching related stuff from
506
+	 * @param \EE_Model_Relation_Base $relation
507
+	 * @param WP_REST_Request         $request
508
+	 * @return array
509
+	 * @throws EE_Error
510
+	 */
511
+	public function getEntitiesFromRelation($id, $relation, $request)
512
+	{
513
+		if (! $relation->get_this_model()->has_primary_key_field()) {
514
+			throw new EE_Error(
515
+				sprintf(
516
+					__(
517
+					// @codingStandardsIgnoreStart
518
+						'Read::get_entities_from_relation should only be called from a model with a primary key, it was called from %1$s',
519
+						// @codingStandardsIgnoreEnd
520
+						'event_espresso'
521
+					),
522
+					$relation->get_this_model()->get_this_model_name()
523
+				)
524
+			);
525
+		}
526
+		// can we edit that main item?
527
+		// if not, show nothing but an error
528
+		// otherwise, please proceed
529
+		return $this->getEntitiesFromRelationUsingModelQueryParams(
530
+			array(
531
+				array(
532
+					$relation->get_this_model()->primary_key_name() => $id,
533
+				),
534
+			),
535
+			$relation,
536
+			$request
537
+		);
538
+	}
539
+
540
+
541
+	/**
542
+	 * Sets the headers that are based on the model and query params,
543
+	 * like the total records. This should only be called on the original request
544
+	 * from the client, not on subsequent internal
545
+	 *
546
+	 * @param EEM_Base $model
547
+	 * @param array    $query_params
548
+	 * @return void
549
+	 */
550
+	protected function setHeadersFromQueryParams($model, $query_params)
551
+	{
552
+		$this->setDebugInfo('model query params', $query_params);
553
+		$this->setDebugInfo(
554
+			'missing caps',
555
+			Capabilities::getMissingPermissionsString($model, $query_params['caps'])
556
+		);
557
+		// normally the limit to a 2-part array, where the 2nd item is the limit
558
+		if (! isset($query_params['limit'])) {
559
+			$query_params['limit'] = EED_Core_Rest_Api::get_default_query_limit();
560
+		}
561
+		if (is_array($query_params['limit'])) {
562
+			$limit_parts = $query_params['limit'];
563
+		} else {
564
+			$limit_parts = explode(',', $query_params['limit']);
565
+			if (count($limit_parts) == 1) {
566
+				$limit_parts = array(0, $limit_parts[0]);
567
+			}
568
+		}
569
+		// remove the group by and having parts of the query, as those will
570
+		// make the sql query return an array of values, instead of just a single value
571
+		unset($query_params['group_by'], $query_params['having'], $query_params['limit']);
572
+		$count = $model->count($query_params, null, true);
573
+		$pages = $count / $limit_parts[1];
574
+		$this->setResponseHeader('Total', $count, false);
575
+		$this->setResponseHeader('PageSize', $limit_parts[1], false);
576
+		$this->setResponseHeader('TotalPages', ceil($pages), false);
577
+	}
578
+
579
+
580
+	/**
581
+	 * Changes database results into REST API entities
582
+	 *
583
+	 * @param EEM_Base $model
584
+	 * @param array $db_row like results from $wpdb->get_results()
585
+	 * @param WP_REST_Request $rest_request
586
+	 * @param string $deprecated no longer used
587
+	 * @return array ready for being converted into json for sending to client
588
+	 * @throws EE_Error
589
+	 * @throws RestException
590
+	 * @throws InvalidDataTypeException
591
+	 * @throws InvalidInterfaceException
592
+	 * @throws InvalidArgumentException
593
+	 * @throws ReflectionException
594
+	 */
595
+	public function createEntityFromWpdbResult($model, $db_row, $rest_request, $deprecated = null)
596
+	{
597
+		if (! $rest_request instanceof WP_REST_Request) {
598
+			// ok so this was called in the old style, where the 3rd arg was
599
+			// $include, and the 4th arg was $context
600
+			// now setup the request just to avoid fatal errors, although we won't be able
601
+			// to truly make use of it because it's kinda devoid of info
602
+			$rest_request = new WP_REST_Request();
603
+			$rest_request->set_param('include', $rest_request);
604
+			$rest_request->set_param('caps', $deprecated);
605
+		}
606
+		if ($rest_request->get_param('caps') == null) {
607
+			$rest_request->set_param('caps', EEM_Base::caps_read);
608
+		}
609
+		$current_user_full_access_to_entity = $model->currentUserCan(
610
+			EEM_Base::caps_read_admin,
611
+			$model->deduce_fields_n_values_from_cols_n_values($db_row)
612
+		);
613
+		$entity_array = $this->createBareEntityFromWpdbResults($model, $db_row);
614
+		$entity_array = $this->addExtraFields($model, $db_row, $entity_array);
615
+		$entity_array['_links'] = $this->getEntityLinks($model, $db_row, $entity_array);
616
+		// when it's a regular read request for a model with a password and the password wasn't provided
617
+		// remove the password protected fields
618
+		$has_protected_fields = false;
619
+		try {
620
+			$this->checkPassword(
621
+				$model,
622
+				$db_row,
623
+				$model->alter_query_params_to_restrict_by_ID(
624
+					$model->get_index_primary_key_string(
625
+						$model->deduce_fields_n_values_from_cols_n_values($db_row)
626
+					)
627
+				),
628
+				$rest_request
629
+			);
630
+		} catch (RestPasswordRequiredException $e) {
631
+			if ($model->hasPassword()) {
632
+				// just remove protected fields
633
+				$has_protected_fields = true;
634
+				$entity_array = Capabilities::filterOutPasswordProtectedFields(
635
+					$entity_array,
636
+					$model,
637
+					$this->getModelVersionInfo()
638
+				);
639
+			} else {
640
+				// that's a problem. None of this should be accessible if no password was provided
641
+				throw $e;
642
+			}
643
+		}
644
+
645
+		$entity_array['_calculated_fields'] = $this->getEntityCalculations($model, $db_row, $rest_request, $has_protected_fields);
646
+		$entity_array = apply_filters(
647
+			'FHEE__Read__create_entity_from_wpdb_results__entity_before_including_requested_models',
648
+			$entity_array,
649
+			$model,
650
+			$rest_request->get_param('caps'),
651
+			$rest_request,
652
+			$this
653
+		);
654
+		// add an empty protected property for now. If it's still around after we remove everything the request didn't
655
+		// want, we'll populate it then. k?
656
+		$entity_array['_protected'] = array();
657
+		// remove any properties the request didn't want. This way _protected won't bother mentioning them
658
+		$entity_array = $this->includeOnlyRequestedProperties($model, $rest_request, $entity_array);
659
+		$entity_array = $this->includeRequestedModels($model, $rest_request, $entity_array, $db_row, $has_protected_fields);
660
+		// if they still wanted the _protected property, add it.
661
+		if (isset($entity_array['_protected'])) {
662
+			$entity_array = $this->addProtectedProperty($model, $entity_array, $has_protected_fields);
663
+		}
664
+		$entity_array = apply_filters(
665
+			'FHEE__Read__create_entity_from_wpdb_results__entity_before_inaccessible_field_removal',
666
+			$entity_array,
667
+			$model,
668
+			$rest_request->get_param('caps'),
669
+			$rest_request,
670
+			$this
671
+		);
672
+		if (! $current_user_full_access_to_entity) {
673
+			$result_without_inaccessible_fields = Capabilities::filterOutInaccessibleEntityFields(
674
+				$entity_array,
675
+				$model,
676
+				$rest_request->get_param('caps'),
677
+				$this->getModelVersionInfo()
678
+			);
679
+		} else {
680
+			$result_without_inaccessible_fields = $entity_array;
681
+		}
682
+		$this->setDebugInfo(
683
+			'inaccessible fields',
684
+			array_keys(array_diff_key((array) $entity_array, (array) $result_without_inaccessible_fields))
685
+		);
686
+		return apply_filters(
687
+			'FHEE__Read__create_entity_from_wpdb_results__entity_return',
688
+			$result_without_inaccessible_fields,
689
+			$model,
690
+			$rest_request->get_param('caps')
691
+		);
692
+	}
693
+
694
+	/**
695
+	 * Returns an array describing which fields can be protected, and which actually were removed this request
696
+	 * @since 4.9.74.p
697
+	 * @param $model
698
+	 * @param $results_so_far
699
+	 * @param $protected
700
+	 * @return array results
701
+	 */
702
+	protected function addProtectedProperty(EEM_Base $model, $results_so_far, $protected)
703
+	{
704
+		if (! $model->hasPassword() || ! $protected) {
705
+			return $results_so_far;
706
+		}
707
+		$password_field = $model->getPasswordField();
708
+		$all_protected = array_merge(
709
+			array($password_field->get_name()),
710
+			$password_field->protectedFields()
711
+		);
712
+		$fields_included = array_keys($results_so_far);
713
+		$fields_included = array_intersect(
714
+			$all_protected,
715
+			$fields_included
716
+		);
717
+		foreach ($fields_included as $field_name) {
718
+			$results_so_far['_protected'][] = $field_name ;
719
+		}
720
+		return $results_so_far;
721
+	}
722
+
723
+	/**
724
+	 * Creates a REST entity array (JSON object we're going to return in the response, but
725
+	 * for now still a PHP array, but soon enough we'll call json_encode on it, don't worry),
726
+	 * from $wpdb->get_row( $sql, ARRAY_A)
727
+	 *
728
+	 * @param EEM_Base $model
729
+	 * @param array    $db_row
730
+	 * @return array entity mostly ready for converting to JSON and sending in the response
731
+	 */
732
+	protected function createBareEntityFromWpdbResults(EEM_Base $model, $db_row)
733
+	{
734
+		$result = $model->deduce_fields_n_values_from_cols_n_values($db_row);
735
+		$result = array_intersect_key(
736
+			$result,
737
+			$this->getModelVersionInfo()->fieldsOnModelInThisVersion($model)
738
+		);
739
+		// if this is a CPT, we need to set the global $post to it,
740
+		// otherwise shortcodes etc won't work properly while rendering it
741
+		if ($model instanceof \EEM_CPT_Base) {
742
+			$do_chevy_shuffle = true;
743
+		} else {
744
+			$do_chevy_shuffle = false;
745
+		}
746
+		if ($do_chevy_shuffle) {
747
+			global $post;
748
+			$old_post = $post;
749
+			$post = get_post($result[ $model->primary_key_name() ]);
750
+			if (! $post instanceof \WP_Post) {
751
+				// well that's weird, because $result is what we JUST fetched from the database
752
+				throw new RestException(
753
+					'error_fetching_post_from_database_results',
754
+					esc_html__(
755
+						'An item was retrieved from the database but it\'s not a WP_Post like it should be.',
756
+						'event_espresso'
757
+					)
758
+				);
759
+			}
760
+			$model_object_classname = 'EE_' . $model->get_this_model_name();
761
+			$post->{$model_object_classname} = \EE_Registry::instance()->load_class(
762
+				$model_object_classname,
763
+				$result,
764
+				false,
765
+				false
766
+			);
767
+		}
768
+		foreach ($result as $field_name => $field_value) {
769
+			$field_obj = $model->field_settings_for($field_name);
770
+			if ($this->isSubclassOfOne($field_obj, $this->getModelVersionInfo()->fieldsIgnored())) {
771
+				unset($result[ $field_name ]);
772
+			} elseif ($this->isSubclassOfOne(
773
+				$field_obj,
774
+				$this->getModelVersionInfo()->fieldsThatHaveRenderedFormat()
775
+			)
776
+			) {
777
+				$result[ $field_name ] = array(
778
+					'raw'      => $this->prepareFieldObjValueForJson($field_obj, $field_value),
779
+					'rendered' => $this->prepareFieldObjValueForJson($field_obj, $field_value, 'pretty'),
780
+				);
781
+			} elseif ($this->isSubclassOfOne(
782
+				$field_obj,
783
+				$this->getModelVersionInfo()->fieldsThatHavePrettyFormat()
784
+			)
785
+			) {
786
+				$result[ $field_name ] = array(
787
+					'raw'    => $this->prepareFieldObjValueForJson($field_obj, $field_value),
788
+					'pretty' => $this->prepareFieldObjValueForJson($field_obj, $field_value, 'pretty'),
789
+				);
790
+			} elseif ($field_obj instanceof \EE_Datetime_Field) {
791
+				$field_value = $field_obj->prepare_for_set_from_db($field_value);
792
+				// if the value is null, but we're not supposed to permit null, then set to the field's default
793
+				if (is_null($field_value)) {
794
+					$field_value = $field_obj->getDefaultDateTimeObj();
795
+				}
796
+				if (is_null($field_value)) {
797
+					$gmt_date = $local_date = ModelDataTranslator::prepareFieldValuesForJson(
798
+						$field_obj,
799
+						$field_value,
800
+						$this->getModelVersionInfo()->requestedVersion()
801
+					);
802
+				} else {
803
+					$timezone = $field_value->getTimezone();
804
+					EEH_DTT_Helper::setTimezone($field_value, new DateTimeZone('UTC'));
805
+					$gmt_date = ModelDataTranslator::prepareFieldValuesForJson(
806
+						$field_obj,
807
+						$field_value,
808
+						$this->getModelVersionInfo()->requestedVersion()
809
+					);
810
+					EEH_DTT_Helper::setTimezone($field_value, $timezone);
811
+					$local_date = ModelDataTranslator::prepareFieldValuesForJson(
812
+						$field_obj,
813
+						$field_value,
814
+						$this->getModelVersionInfo()->requestedVersion()
815
+					);
816
+				}
817
+				$result[ $field_name . '_gmt' ] = $gmt_date;
818
+				$result[ $field_name ] = $local_date;
819
+			} else {
820
+				$result[ $field_name ] = $this->prepareFieldObjValueForJson($field_obj, $field_value);
821
+			}
822
+		}
823
+		if ($do_chevy_shuffle) {
824
+			$post = $old_post;
825
+		}
826
+		return $result;
827
+	}
828
+
829
+
830
+	/**
831
+	 * Takes a value all the way from the DB representation, to the model object's representation, to the
832
+	 * user-facing PHP representation, to the REST API representation. (Assumes you've already taken from the DB
833
+	 * representation using $field_obj->prepare_for_set_from_db())
834
+	 *
835
+	 * @param EE_Model_Field_Base $field_obj
836
+	 * @param mixed               $value  as it's stored on a model object
837
+	 * @param string              $format valid values are 'normal' (default), 'pretty', 'datetime_obj'
838
+	 * @return mixed
839
+	 * @throws ObjectDetectedException if $value contains a PHP object
840
+	 */
841
+	protected function prepareFieldObjValueForJson(EE_Model_Field_Base $field_obj, $value, $format = 'normal')
842
+	{
843
+		$value = $field_obj->prepare_for_set_from_db($value);
844
+		switch ($format) {
845
+			case 'pretty':
846
+				$value = $field_obj->prepare_for_pretty_echoing($value);
847
+				break;
848
+			case 'normal':
849
+			default:
850
+				$value = $field_obj->prepare_for_get($value);
851
+				break;
852
+		}
853
+		return ModelDataTranslator::prepareFieldValuesForJson(
854
+			$field_obj,
855
+			$value,
856
+			$this->getModelVersionInfo()->requestedVersion()
857
+		);
858
+	}
859
+
860
+
861
+	/**
862
+	 * Adds a few extra fields to the entity response
863
+	 *
864
+	 * @param EEM_Base $model
865
+	 * @param array    $db_row
866
+	 * @param array    $entity_array
867
+	 * @return array modified entity
868
+	 */
869
+	protected function addExtraFields(EEM_Base $model, $db_row, $entity_array)
870
+	{
871
+		if ($model instanceof EEM_CPT_Base) {
872
+			$entity_array['link'] = get_permalink($db_row[ $model->get_primary_key_field()->get_qualified_column() ]);
873
+		}
874
+		return $entity_array;
875
+	}
876
+
877
+
878
+	/**
879
+	 * Gets links we want to add to the response
880
+	 *
881
+	 * @global \WP_REST_Server $wp_rest_server
882
+	 * @param EEM_Base         $model
883
+	 * @param array            $db_row
884
+	 * @param array            $entity_array
885
+	 * @return array the _links item in the entity
886
+	 */
887
+	protected function getEntityLinks($model, $db_row, $entity_array)
888
+	{
889
+		// add basic links
890
+		$links = array();
891
+		if ($model->has_primary_key_field()) {
892
+			$links['self'] = array(
893
+				array(
894
+					'href' => $this->getVersionedLinkTo(
895
+						EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
896
+						. '/'
897
+						. $entity_array[ $model->primary_key_name() ]
898
+					),
899
+				),
900
+			);
901
+		}
902
+		$links['collection'] = array(
903
+			array(
904
+				'href' => $this->getVersionedLinkTo(
905
+					EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
906
+				),
907
+			),
908
+		);
909
+		// add links to related models
910
+		if ($model->has_primary_key_field()) {
911
+			foreach ($this->getModelVersionInfo()->relationSettings($model) as $relation_name => $relation_obj) {
912
+				$related_model_part = Read::getRelatedEntityName($relation_name, $relation_obj);
913
+				$links[ EED_Core_Rest_Api::ee_api_link_namespace . $related_model_part ] = array(
914
+					array(
915
+						'href'   => $this->getVersionedLinkTo(
916
+							EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
917
+							. '/'
918
+							. $entity_array[ $model->primary_key_name() ]
919
+							. '/'
920
+							. $related_model_part
921
+						),
922
+						'single' => $relation_obj instanceof EE_Belongs_To_Relation ? true : false,
923
+					),
924
+				);
925
+			}
926
+		}
927
+		return $links;
928
+	}
929
+
930
+
931
+	/**
932
+	 * Adds the included models indicated in the request to the entity provided
933
+	 *
934
+	 * @param EEM_Base $model
935
+	 * @param WP_REST_Request $rest_request
936
+	 * @param array $entity_array
937
+	 * @param array $db_row
938
+	 * @param boolean $included_items_protected if the original item is password protected, don't include any related models.
939
+	 * @return array the modified entity
940
+	 * @throws RestException
941
+	 */
942
+	protected function includeRequestedModels(
943
+		EEM_Base $model,
944
+		WP_REST_Request $rest_request,
945
+		$entity_array,
946
+		$db_row = array(),
947
+		$included_items_protected = false
948
+	) {
949
+		// if $db_row not included, hope the entity array has what we need
950
+		if (! $db_row) {
951
+			$db_row = $entity_array;
952
+		}
953
+		$relation_settings = $this->getModelVersionInfo()->relationSettings($model);
954
+		foreach ($relation_settings as $relation_name => $relation_obj) {
955
+			$related_fields_to_include = $this->explodeAndGetItemsPrefixedWith(
956
+				$rest_request->get_param('include'),
957
+				$relation_name
958
+			);
959
+			$related_fields_to_calculate = $this->explodeAndGetItemsPrefixedWith(
960
+				$rest_request->get_param('calculate'),
961
+				$relation_name
962
+			);
963
+			// did they specify they wanted to include a related model, or
964
+			// specific fields from a related model?
965
+			// or did they specify to calculate a field from a related model?
966
+			if ($related_fields_to_include || $related_fields_to_calculate) {
967
+				// if so, we should include at least some part of the related model
968
+				$pretend_related_request = new WP_REST_Request();
969
+				$pretend_related_request->set_query_params(
970
+					array(
971
+						'caps'      => $rest_request->get_param('caps'),
972
+						'include'   => $related_fields_to_include,
973
+						'calculate' => $related_fields_to_calculate,
974
+						'password' => $rest_request->get_param('password')
975
+					)
976
+				);
977
+				$pretend_related_request->add_header('no_rest_headers', true);
978
+				$primary_model_query_params = $model->alter_query_params_to_restrict_by_ID(
979
+					$model->get_index_primary_key_string(
980
+						$model->deduce_fields_n_values_from_cols_n_values($db_row)
981
+					)
982
+				);
983
+				if (! $included_items_protected) {
984
+					try {
985
+						$related_results = $this->getEntitiesFromRelationUsingModelQueryParams(
986
+							$primary_model_query_params,
987
+							$relation_obj,
988
+							$pretend_related_request
989
+						);
990
+					} catch (RestException $e) {
991
+						$related_results = null;
992
+					}
993
+				} else {
994
+					// they're protected, hide them.
995
+					$related_results = null;
996
+					$entity_array['_protected'][] = Read::getRelatedEntityName($relation_name, $relation_obj);
997
+				}
998
+				if ($related_results instanceof WP_Error || $related_results === null) {
999
+					$related_results = $relation_obj instanceof EE_Belongs_To_Relation ? null : array();
1000
+				}
1001
+				$entity_array[ Read::getRelatedEntityName($relation_name, $relation_obj) ] = $related_results;
1002
+			}
1003
+		}
1004
+		return $entity_array;
1005
+	}
1006
+
1007
+	/**
1008
+	 * If the user has requested only specific properties (including meta properties like _links or _protected)
1009
+	 * remove everything else.
1010
+	 * @since 4.9.74.p
1011
+	 * @param EEM_Base $model
1012
+	 * @param WP_REST_Request $rest_request
1013
+	 * @param $entity_array
1014
+	 * @return array
1015
+	 * @throws EE_Error
1016
+	 */
1017
+	protected function includeOnlyRequestedProperties(
1018
+		EEM_Base $model,
1019
+		WP_REST_Request $rest_request,
1020
+		$entity_array
1021
+	) {
1022
+
1023
+		$includes_for_this_model = $this->explodeAndGetItemsPrefixedWith($rest_request->get_param('include'), '');
1024
+		$includes_for_this_model = $this->removeModelNamesFromArray($includes_for_this_model);
1025
+		// if they passed in * or didn't specify any includes, return everything
1026
+		if (! in_array('*', $includes_for_this_model)
1027
+			&& ! empty($includes_for_this_model)
1028
+		) {
1029
+			if ($model->has_primary_key_field()) {
1030
+				// always include the primary key. ya just gotta know that at least
1031
+				$includes_for_this_model[] = $model->primary_key_name();
1032
+			}
1033
+			if ($this->explodeAndGetItemsPrefixedWith($rest_request->get_param('calculate'), '')) {
1034
+				$includes_for_this_model[] = '_calculated_fields';
1035
+			}
1036
+			$entity_array = array_intersect_key($entity_array, array_flip($includes_for_this_model));
1037
+		}
1038
+		return $entity_array;
1039
+	}
1040
+
1041
+
1042
+	/**
1043
+	 * Returns a new array with all the names of models removed. Eg
1044
+	 * array( 'Event', 'Datetime.*', 'foobar' ) would become array( 'Datetime.*', 'foobar' )
1045
+	 *
1046
+	 * @param array $arr
1047
+	 * @return array
1048
+	 */
1049
+	private function removeModelNamesFromArray($arr)
1050
+	{
1051
+		return array_diff($arr, array_keys(EE_Registry::instance()->non_abstract_db_models));
1052
+	}
1053
+
1054
+
1055
+	/**
1056
+	 * Gets the calculated fields for the response
1057
+	 *
1058
+	 * @param EEM_Base        $model
1059
+	 * @param array           $wpdb_row
1060
+	 * @param WP_REST_Request $rest_request
1061
+	 * @param boolean $row_is_protected whether this row is password protected or not
1062
+	 * @return \stdClass the _calculations item in the entity
1063
+	 * @throws ObjectDetectedException if a default value has a PHP object, which should never do (and if we
1064
+	 * did, let's know about it ASAP, so let the exception bubble up)
1065
+	 */
1066
+	protected function getEntityCalculations($model, $wpdb_row, $rest_request, $row_is_protected = false)
1067
+	{
1068
+		$calculated_fields = $this->explodeAndGetItemsPrefixedWith(
1069
+			$rest_request->get_param('calculate'),
1070
+			''
1071
+		);
1072
+		// note: setting calculate=* doesn't do anything
1073
+		$calculated_fields_to_return = new \stdClass();
1074
+		$protected_fields = array();
1075
+		foreach ($calculated_fields as $field_to_calculate) {
1076
+			try {
1077
+				// it's password protected, so they shouldn't be able to read this. Remove the value
1078
+				$schema = $this->fields_calculator->getJsonSchemaForModel($model);
1079
+				if ($row_is_protected
1080
+					&& isset($schema['properties'][ $field_to_calculate ]['protected'])
1081
+					&& $schema['properties'][ $field_to_calculate ]['protected']) {
1082
+					$calculated_value = null;
1083
+					$protected_fields[] = $field_to_calculate;
1084
+					if ($schema['properties'][ $field_to_calculate ]['type']) {
1085
+						switch ($schema['properties'][ $field_to_calculate ]['type']) {
1086
+							case 'boolean':
1087
+								$calculated_value = false;
1088
+								break;
1089
+							case 'integer':
1090
+								$calculated_value = 0;
1091
+								break;
1092
+							case 'string':
1093
+								$calculated_value = '';
1094
+								break;
1095
+							case 'array':
1096
+								$calculated_value = array();
1097
+								break;
1098
+							case 'object':
1099
+								$calculated_value = new stdClass();
1100
+								break;
1101
+						}
1102
+					}
1103
+				} else {
1104
+					$calculated_value = ModelDataTranslator::prepareFieldValueForJson(
1105
+						null,
1106
+						$this->fields_calculator->retrieveCalculatedFieldValue(
1107
+							$model,
1108
+							$field_to_calculate,
1109
+							$wpdb_row,
1110
+							$rest_request,
1111
+							$this
1112
+						),
1113
+						$this->getModelVersionInfo()->requestedVersion()
1114
+					);
1115
+				}
1116
+				$calculated_fields_to_return->{$field_to_calculate} = $calculated_value;
1117
+			} catch (RestException $e) {
1118
+				// if we don't have permission to read it, just leave it out. but let devs know about the problem
1119
+				$this->setResponseHeader(
1120
+					'Notices-Field-Calculation-Errors['
1121
+					. $e->getStringCode()
1122
+					. ']['
1123
+					. $model->get_this_model_name()
1124
+					. ']['
1125
+					. $field_to_calculate
1126
+					. ']',
1127
+					$e->getMessage(),
1128
+					true
1129
+				);
1130
+			}
1131
+		}
1132
+		$calculated_fields_to_return->_protected = $protected_fields;
1133
+		return $calculated_fields_to_return;
1134
+	}
1135
+
1136
+
1137
+	/**
1138
+	 * Gets the full URL to the resource, taking the requested version into account
1139
+	 *
1140
+	 * @param string $link_part_after_version_and_slash eg "events/10/datetimes"
1141
+	 * @return string url eg "http://mysite.com/wp-json/ee/v4.6/events/10/datetimes"
1142
+	 */
1143
+	public function getVersionedLinkTo($link_part_after_version_and_slash)
1144
+	{
1145
+		return rest_url(
1146
+			EED_Core_Rest_Api::get_versioned_route_to(
1147
+				$link_part_after_version_and_slash,
1148
+				$this->getModelVersionInfo()->requestedVersion()
1149
+			)
1150
+		);
1151
+	}
1152
+
1153
+
1154
+	/**
1155
+	 * Gets the correct lowercase name for the relation in the API according
1156
+	 * to the relation's type
1157
+	 *
1158
+	 * @param string                  $relation_name
1159
+	 * @param \EE_Model_Relation_Base $relation_obj
1160
+	 * @return string
1161
+	 */
1162
+	public static function getRelatedEntityName($relation_name, $relation_obj)
1163
+	{
1164
+		if ($relation_obj instanceof EE_Belongs_To_Relation) {
1165
+			return strtolower($relation_name);
1166
+		} else {
1167
+			return EEH_Inflector::pluralize_and_lower($relation_name);
1168
+		}
1169
+	}
1170
+
1171
+
1172
+	/**
1173
+	 * Gets the one model object with the specified id for the specified model
1174
+	 *
1175
+	 * @param EEM_Base        $model
1176
+	 * @param WP_REST_Request $request
1177
+	 * @return array
1178
+	 */
1179
+	public function getEntityFromModel($model, $request)
1180
+	{
1181
+		$context = $this->validateContext($request->get_param('caps'));
1182
+		return $this->getOneOrReportPermissionError($model, $request, $context);
1183
+	}
1184
+
1185
+
1186
+	/**
1187
+	 * If a context is provided which isn't valid, maybe it was added in a future
1188
+	 * version so just treat it as a default read
1189
+	 *
1190
+	 * @param string $context
1191
+	 * @return string array key of EEM_Base::cap_contexts_to_cap_action_map()
1192
+	 */
1193
+	public function validateContext($context)
1194
+	{
1195
+		if (! $context) {
1196
+			$context = EEM_Base::caps_read;
1197
+		}
1198
+		$valid_contexts = EEM_Base::valid_cap_contexts();
1199
+		if (in_array($context, $valid_contexts)) {
1200
+			return $context;
1201
+		} else {
1202
+			return EEM_Base::caps_read;
1203
+		}
1204
+	}
1205
+
1206
+
1207
+	/**
1208
+	 * Verifies the passed in value is an allowable default where conditions value.
1209
+	 *
1210
+	 * @param $default_query_params
1211
+	 * @return string
1212
+	 */
1213
+	public function validateDefaultQueryParams($default_query_params)
1214
+	{
1215
+		$valid_default_where_conditions_for_api_calls = array(
1216
+			EEM_Base::default_where_conditions_all,
1217
+			EEM_Base::default_where_conditions_minimum_all,
1218
+			EEM_Base::default_where_conditions_minimum_others,
1219
+		);
1220
+		if (! $default_query_params) {
1221
+			$default_query_params = EEM_Base::default_where_conditions_all;
1222
+		}
1223
+		if (in_array(
1224
+			$default_query_params,
1225
+			$valid_default_where_conditions_for_api_calls,
1226
+			true
1227
+		)) {
1228
+			return $default_query_params;
1229
+		} else {
1230
+			return EEM_Base::default_where_conditions_all;
1231
+		}
1232
+	}
1233
+
1234
+
1235
+	/**
1236
+	 * Translates API filter get parameter into model query params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions.
1237
+	 * Note: right now the query parameter keys for fields (and related fields)
1238
+	 * can be left as-is, but it's quite possible this will change someday.
1239
+	 * Also, this method's contents might be candidate for moving to Model_Data_Translator
1240
+	 *
1241
+	 * @param EEM_Base $model
1242
+	 * @param array    $query_parameters  from $_GET parameter @see Read:handle_request_get_all
1243
+	 * @return array model query params (@see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions)
1244
+	 *                                    or FALSE to indicate that absolutely no results should be returned
1245
+	 * @throws EE_Error
1246
+	 * @throws RestException
1247
+	 */
1248
+	public function createModelQueryParams($model, $query_params)
1249
+	{
1250
+		$model_query_params = array();
1251
+		if (isset($query_params['where'])) {
1252
+			$model_query_params[0] = ModelDataTranslator::prepareConditionsQueryParamsForModels(
1253
+				$query_params['where'],
1254
+				$model,
1255
+				$this->getModelVersionInfo()->requestedVersion()
1256
+			);
1257
+		}
1258
+		if (isset($query_params['order_by'])) {
1259
+			$order_by = $query_params['order_by'];
1260
+		} elseif (isset($query_params['orderby'])) {
1261
+			$order_by = $query_params['orderby'];
1262
+		} else {
1263
+			$order_by = null;
1264
+		}
1265
+		if ($order_by !== null) {
1266
+			if (is_array($order_by)) {
1267
+				$order_by = ModelDataTranslator::prepareFieldNamesInArrayKeysFromJson($order_by);
1268
+			} else {
1269
+				// it's a single item
1270
+				$order_by = ModelDataTranslator::prepareFieldNameFromJson($order_by);
1271
+			}
1272
+			$model_query_params['order_by'] = $order_by;
1273
+		}
1274
+		if (isset($query_params['group_by'])) {
1275
+			$group_by = $query_params['group_by'];
1276
+		} elseif (isset($query_params['groupby'])) {
1277
+			$group_by = $query_params['groupby'];
1278
+		} else {
1279
+			$group_by = array_keys($model->get_combined_primary_key_fields());
1280
+		}
1281
+		// make sure they're all real names
1282
+		if (is_array($group_by)) {
1283
+			$group_by = ModelDataTranslator::prepareFieldNamesFromJson($group_by);
1284
+		}
1285
+		if ($group_by !== null) {
1286
+			$model_query_params['group_by'] = $group_by;
1287
+		}
1288
+		if (isset($query_params['having'])) {
1289
+			$model_query_params['having'] = ModelDataTranslator::prepareConditionsQueryParamsForModels(
1290
+				$query_params['having'],
1291
+				$model,
1292
+				$this->getModelVersionInfo()->requestedVersion()
1293
+			);
1294
+		}
1295
+		if (isset($query_params['order'])) {
1296
+			$model_query_params['order'] = $query_params['order'];
1297
+		}
1298
+		if (isset($query_params['mine'])) {
1299
+			$model_query_params = $model->alter_query_params_to_only_include_mine($model_query_params);
1300
+		}
1301
+		if (isset($query_params['limit'])) {
1302
+			// limit should be either a string like '23' or '23,43', or an array with two items in it
1303
+			if (! is_array($query_params['limit'])) {
1304
+				$limit_array = explode(',', (string) $query_params['limit']);
1305
+			} else {
1306
+				$limit_array = $query_params['limit'];
1307
+			}
1308
+			$sanitized_limit = array();
1309
+			foreach ($limit_array as $key => $limit_part) {
1310
+				if ($this->debug_mode && (! is_numeric($limit_part) || count($sanitized_limit) > 2)) {
1311
+					throw new EE_Error(
1312
+						sprintf(
1313
+							__(
1314
+							// @codingStandardsIgnoreStart
1315
+								'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.',
1316
+								// @codingStandardsIgnoreEnd
1317
+								'event_espresso'
1318
+							),
1319
+							wp_json_encode($query_params['limit'])
1320
+						)
1321
+					);
1322
+				}
1323
+				$sanitized_limit[] = (int) $limit_part;
1324
+			}
1325
+			$model_query_params['limit'] = implode(',', $sanitized_limit);
1326
+		} else {
1327
+			$model_query_params['limit'] = EED_Core_Rest_Api::get_default_query_limit();
1328
+		}
1329
+		if (isset($query_params['caps'])) {
1330
+			$model_query_params['caps'] = $this->validateContext($query_params['caps']);
1331
+		} else {
1332
+			$model_query_params['caps'] = EEM_Base::caps_read;
1333
+		}
1334
+		if (isset($query_params['default_where_conditions'])) {
1335
+			$model_query_params['default_where_conditions'] = $this->validateDefaultQueryParams(
1336
+				$query_params['default_where_conditions']
1337
+			);
1338
+		}
1339
+		// if this is a model protected by a password on another model, exclude the password protected
1340
+		// entities by default. But if they passed in a password, try to show them all. If the password is wrong,
1341
+		// though, they'll get an error (see Read::createEntityFromWpdbResult() which calls Read::checkPassword)
1342
+		if (! $model->hasPassword()
1343
+			&& $model->restrictedByRelatedModelPassword()
1344
+			&& $model_query_params['caps'] === EEM_Base::caps_read) {
1345
+			if (empty($query_params['password'])) {
1346
+				$model_query_params['exclude_protected'] = true;
1347
+			}
1348
+		}
1349
+
1350
+		return apply_filters('FHEE__Read__create_model_query_params', $model_query_params, $query_params, $model);
1351
+	}
1352
+
1353
+
1354
+	/**
1355
+	 * Changes the REST-style query params for use in the models
1356
+	 *
1357
+	 * @deprecated
1358
+	 * @param EEM_Base $model
1359
+	 * @param array    $query_params sub-array from @see EEM_Base::get_all()
1360
+	 * @return array
1361
+	 */
1362
+	public function prepareRestQueryParamsKeyForModels($model, $query_params)
1363
+	{
1364
+		$model_ready_query_params = array();
1365
+		foreach ($query_params as $key => $value) {
1366
+			if (is_array($value)) {
1367
+				$model_ready_query_params[ $key ] = $this->prepareRestQueryParamsKeyForModels($model, $value);
1368
+			} else {
1369
+				$model_ready_query_params[ $key ] = $value;
1370
+			}
1371
+		}
1372
+		return $model_ready_query_params;
1373
+	}
1374
+
1375
+
1376
+	/**
1377
+	 * @deprecated instead use ModelDataTranslator::prepareFieldValuesFromJson()
1378
+	 * @param $model
1379
+	 * @param $query_params
1380
+	 * @return array
1381
+	 */
1382
+	public function prepareRestQueryParamsValuesForModels($model, $query_params)
1383
+	{
1384
+		$model_ready_query_params = array();
1385
+		foreach ($query_params as $key => $value) {
1386
+			if (is_array($value)) {
1387
+				$model_ready_query_params[ $key ] = $this->prepareRestQueryParamsValuesForModels($model, $value);
1388
+			} else {
1389
+				$model_ready_query_params[ $key ] = $value;
1390
+			}
1391
+		}
1392
+		return $model_ready_query_params;
1393
+	}
1394
+
1395
+
1396
+	/**
1397
+	 * Explodes the string on commas, and only returns items with $prefix followed by a period.
1398
+	 * If no prefix is specified, returns items with no period.
1399
+	 *
1400
+	 * @param string|array $string_to_explode eg "jibba,jabba, blah, blah, blah" or array('jibba', 'jabba' )
1401
+	 * @param string       $prefix            "Event" or "foobar"
1402
+	 * @return array $string_to_exploded exploded on COMMAS, and if a prefix was specified
1403
+	 *                                        we only return strings starting with that and a period; if no prefix was
1404
+	 *                                        specified we return all items containing NO periods
1405
+	 */
1406
+	public function explodeAndGetItemsPrefixedWith($string_to_explode, $prefix)
1407
+	{
1408
+		if (is_string($string_to_explode)) {
1409
+			$exploded_contents = explode(',', $string_to_explode);
1410
+		} elseif (is_array($string_to_explode)) {
1411
+			$exploded_contents = $string_to_explode;
1412
+		} else {
1413
+			$exploded_contents = array();
1414
+		}
1415
+		// if the string was empty, we want an empty array
1416
+		$exploded_contents = array_filter($exploded_contents);
1417
+		$contents_with_prefix = array();
1418
+		foreach ($exploded_contents as $item) {
1419
+			$item = trim($item);
1420
+			// if no prefix was provided, so we look for items with no "." in them
1421
+			if (! $prefix) {
1422
+				// does this item have a period?
1423
+				if (strpos($item, '.') === false) {
1424
+					// if not, then its what we're looking for
1425
+					$contents_with_prefix[] = $item;
1426
+				}
1427
+			} elseif (strpos($item, $prefix . '.') === 0) {
1428
+				// this item has the prefix and a period, grab it
1429
+				$contents_with_prefix[] = substr(
1430
+					$item,
1431
+					strpos($item, $prefix . '.') + strlen($prefix . '.')
1432
+				);
1433
+			} elseif ($item === $prefix) {
1434
+				// this item is JUST the prefix
1435
+				// so let's grab everything after, which is a blank string
1436
+				$contents_with_prefix[] = '';
1437
+			}
1438
+		}
1439
+		return $contents_with_prefix;
1440
+	}
1441
+
1442
+
1443
+	/**
1444
+	 * @deprecated since 4.8.36.rc.001 You should instead use Read::explode_and_get_items_prefixed_with.
1445
+	 * Deprecated because its return values were really quite confusing- sometimes it returned
1446
+	 * an empty array (when the include string was blank or '*') or sometimes it returned
1447
+	 * array('*') (when you provided a model and a model of that kind was found).
1448
+	 * Parses the $include_string so we fetch all the field names relating to THIS model
1449
+	 * (ie have NO period in them), or for the provided model (ie start with the model
1450
+	 * name and then a period).
1451
+	 * @param string $include_string @see Read:handle_request_get_all
1452
+	 * @param string $model_name
1453
+	 * @return array of fields for this model. If $model_name is provided, then
1454
+	 *                               the fields for that model, with the model's name removed from each.
1455
+	 *                               If $include_string was blank or '*' returns an empty array
1456
+	 */
1457
+	public function extractIncludesForThisModel($include_string, $model_name = null)
1458
+	{
1459
+		if (is_array($include_string)) {
1460
+			$include_string = implode(',', $include_string);
1461
+		}
1462
+		if ($include_string === '*' || $include_string === '') {
1463
+			return array();
1464
+		}
1465
+		$includes = explode(',', $include_string);
1466
+		$extracted_fields_to_include = array();
1467
+		if ($model_name) {
1468
+			foreach ($includes as $field_to_include) {
1469
+				$field_to_include = trim($field_to_include);
1470
+				if (strpos($field_to_include, $model_name . '.') === 0) {
1471
+					// found the model name at the exact start
1472
+					$field_sans_model_name = str_replace($model_name . '.', '', $field_to_include);
1473
+					$extracted_fields_to_include[] = $field_sans_model_name;
1474
+				} elseif ($field_to_include == $model_name) {
1475
+					$extracted_fields_to_include[] = '*';
1476
+				}
1477
+			}
1478
+		} else {
1479
+			// look for ones with no period
1480
+			foreach ($includes as $field_to_include) {
1481
+				$field_to_include = trim($field_to_include);
1482
+				if (strpos($field_to_include, '.') === false
1483
+					&& ! $this->getModelVersionInfo()->isModelNameInThisVersion($field_to_include)
1484
+				) {
1485
+					$extracted_fields_to_include[] = $field_to_include;
1486
+				}
1487
+			}
1488
+		}
1489
+		return $extracted_fields_to_include;
1490
+	}
1491
+
1492
+
1493
+	/**
1494
+	 * Gets the single item using the model according to the request in the context given, otherwise
1495
+	 * returns that it's inaccessible to the current user
1496
+	 *
1497
+	 * @param EEM_Base $model
1498
+	 * @param WP_REST_Request $request
1499
+	 * @param null $context
1500
+	 * @return array
1501
+	 * @throws EE_Error
1502
+	 */
1503
+	public function getOneOrReportPermissionError(EEM_Base $model, WP_REST_Request $request, $context = null)
1504
+	{
1505
+		$query_params = array(array($model->primary_key_name() => $request->get_param('id')), 'limit' => 1);
1506
+		if ($model instanceof EEM_Soft_Delete_Base) {
1507
+			$query_params = $model->alter_query_params_so_deleted_and_undeleted_items_included($query_params);
1508
+		}
1509
+		$restricted_query_params = $query_params;
1510
+		$restricted_query_params['caps'] = $context;
1511
+		$this->setDebugInfo('model query params', $restricted_query_params);
1512
+		$model_rows = $model->get_all_wpdb_results($restricted_query_params);
1513
+		if (! empty($model_rows)) {
1514
+			return $this->createEntityFromWpdbResult(
1515
+				$model,
1516
+				reset($model_rows),
1517
+				$request
1518
+			);
1519
+		} else {
1520
+			// ok let's test to see if we WOULD have found it, had we not had restrictions from missing capabilities
1521
+			$lowercase_model_name = strtolower($model->get_this_model_name());
1522
+			if ($model->exists($query_params)) {
1523
+				// you got shafted- it existed but we didn't want to tell you!
1524
+				throw new RestException(
1525
+					'rest_user_cannot_' . $context,
1526
+					sprintf(
1527
+						__('Sorry, you cannot %1$s this %2$s. Missing permissions are: %3$s', 'event_espresso'),
1528
+						$context,
1529
+						$lowercase_model_name,
1530
+						Capabilities::getMissingPermissionsString(
1531
+							$model,
1532
+							$context
1533
+						)
1534
+					),
1535
+					array('status' => 403)
1536
+				);
1537
+			} else {
1538
+				// it's not you. It just doesn't exist
1539
+				throw new RestException(
1540
+					sprintf('rest_%s_invalid_id', $lowercase_model_name),
1541
+					sprintf(__('Invalid %s ID.', 'event_espresso'), $lowercase_model_name),
1542
+					array('status' => 404)
1543
+				);
1544
+			}
1545
+		}
1546
+	}
1547
+
1548
+	/**
1549
+	 * Checks that if this content requires a password to be read, that it's been provided and is correct.
1550
+	 * @since 4.9.74.p
1551
+	 * @param EEM_Base $model
1552
+	 * @param $model_row
1553
+	 * @param $query_params Adds 'default_where_conditions' => 'minimum' to ensure we don't confuse trashed with
1554
+	 *                      password protected.
1555
+	 * @param WP_REST_Request $request
1556
+	 * @throws EE_Error
1557
+	 * @throws InvalidArgumentException
1558
+	 * @throws InvalidDataTypeException
1559
+	 * @throws InvalidInterfaceException
1560
+	 * @throws RestPasswordRequiredException
1561
+	 * @throws RestPasswordIncorrectException
1562
+	 * @throws \EventEspresso\core\exceptions\ModelConfigurationException
1563
+	 * @throws ReflectionException
1564
+	 */
1565
+	protected function checkPassword(EEM_Base $model, $model_row, $query_params, WP_REST_Request $request)
1566
+	{
1567
+		$query_params['default_where_conditions'] = 'minimum';
1568
+		// stuff is only "protected" for front-end requests. Elsewhere, you either get full permission to access the object
1569
+		// or you don't.
1570
+		$request_caps = $request->get_param('caps');
1571
+		if (isset($request_caps) && $request_caps !== EEM_Base::caps_read) {
1572
+			return;
1573
+		}
1574
+		// if this entity requires a password, they better give it and it better be right!
1575
+		if ($model->hasPassword()
1576
+			&& $model_row[ $model->getPasswordField()->get_qualified_column() ] !== '') {
1577
+			if (empty($request['password'])) {
1578
+				throw new RestPasswordRequiredException();
1579
+			} elseif (!hash_equals(
1580
+				$model_row[ $model->getPasswordField()->get_qualified_column() ],
1581
+				$request['password']
1582
+			)) {
1583
+				throw new RestPasswordIncorrectException();
1584
+			}
1585
+		} // wait! maybe this content is password protected
1586
+		elseif ($model->restrictedByRelatedModelPassword()
1587
+			&& $request->get_param('caps') === EEM_Base::caps_read) {
1588
+			$password_supplied = $request->get_param('password');
1589
+			if (empty($password_supplied)) {
1590
+				$query_params['exclude_protected'] = true;
1591
+				if (!$model->exists($query_params)) {
1592
+					throw new RestPasswordRequiredException();
1593
+				}
1594
+			} else {
1595
+				$query_params[0][ $model->modelChainAndPassword() ] = $password_supplied;
1596
+				if (!$model->exists($query_params)) {
1597
+					throw new RestPasswordIncorrectException();
1598
+				}
1599
+			}
1600
+		}
1601
+	}
1602 1602
 }
Please login to merge, or discard this patch.
Spacing   +68 added lines, -68 removed lines patch added patch discarded remove patch
@@ -78,7 +78,7 @@  discard block
 block discarded – undo
78 78
         $controller = LoaderFactory::getLoader()->getNew('EventEspresso\core\libraries\rest_api\controllers\model\Read');
79 79
         try {
80 80
             $controller->setRequestedVersion($version);
81
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
81
+            if ( ! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
82 82
                 return $controller->sendResponse(
83 83
                     new WP_Error(
84 84
                         'endpoint_parsing_error',
@@ -119,7 +119,7 @@  discard block
 block discarded – undo
119 119
         $controller = LoaderFactory::getLoader()->getNew('EventEspresso\core\libraries\rest_api\controllers\model\Read');
120 120
         try {
121 121
             $controller->setRequestedVersion($version);
122
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
122
+            if ( ! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
123 123
                 return array();
124 124
             }
125 125
             // get the model for this version
@@ -176,11 +176,11 @@  discard block
 block discarded – undo
176 176
      */
177 177
     protected function translateDefaultsForRestResponse($field_name, EE_Model_Field_Base $field, array $schema)
178 178
     {
179
-        if (isset($schema['properties'][ $field_name ]['default'])) {
180
-            if (is_array($schema['properties'][ $field_name ]['default'])) {
181
-                foreach ($schema['properties'][ $field_name ]['default'] as $default_key => $default_value) {
179
+        if (isset($schema['properties'][$field_name]['default'])) {
180
+            if (is_array($schema['properties'][$field_name]['default'])) {
181
+                foreach ($schema['properties'][$field_name]['default'] as $default_key => $default_value) {
182 182
                     if ($default_key === 'raw') {
183
-                        $schema['properties'][ $field_name ]['default'][ $default_key ] =
183
+                        $schema['properties'][$field_name]['default'][$default_key] =
184 184
                             ModelDataTranslator::prepareFieldValueForJson(
185 185
                                 $field,
186 186
                                 $default_value,
@@ -189,9 +189,9 @@  discard block
 block discarded – undo
189 189
                     }
190 190
                 }
191 191
             } else {
192
-                $schema['properties'][ $field_name ]['default'] = ModelDataTranslator::prepareFieldValueForJson(
192
+                $schema['properties'][$field_name]['default'] = ModelDataTranslator::prepareFieldValueForJson(
193 193
                     $field,
194
-                    $schema['properties'][ $field_name ]['default'],
194
+                    $schema['properties'][$field_name]['default'],
195 195
                     $this->getModelVersionInfo()->requestedVersion()
196 196
                 );
197 197
             }
@@ -213,9 +213,9 @@  discard block
 block discarded – undo
213 213
     protected function maybeAddExtraFieldsToSchema($field_name, EE_Model_Field_Base $field, array $schema)
214 214
     {
215 215
         if ($field instanceof EE_Datetime_Field) {
216
-            $schema['properties'][ $field_name . '_gmt' ] = $field->getSchema();
216
+            $schema['properties'][$field_name.'_gmt'] = $field->getSchema();
217 217
             // modify the description
218
-            $schema['properties'][ $field_name . '_gmt' ]['description'] = sprintf(
218
+            $schema['properties'][$field_name.'_gmt']['description'] = sprintf(
219 219
                 esc_html__('%s - the value for this field is in GMT.', 'event_espresso'),
220 220
                 wp_specialchars_decode($field->get_nicename(), ENT_QUOTES)
221 221
             );
@@ -258,7 +258,7 @@  discard block
 block discarded – undo
258 258
         $controller = LoaderFactory::getLoader()->getNew('EventEspresso\core\libraries\rest_api\controllers\model\Read');
259 259
         try {
260 260
             $controller->setRequestedVersion($version);
261
-            if (! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
261
+            if ( ! $controller->getModelVersionInfo()->isModelNameInThisVersion($model_name)) {
262 262
                 return $controller->sendResponse(
263 263
                     new WP_Error(
264 264
                         'endpoint_parsing_error',
@@ -337,7 +337,7 @@  discard block
 block discarded – undo
337 337
     public function getEntitiesFromModel($model, $request)
338 338
     {
339 339
         $query_params = $this->createModelQueryParams($model, $request->get_params());
340
-        if (! Capabilities::currentUserHasPartialAccessTo($model, $query_params['caps'])) {
340
+        if ( ! Capabilities::currentUserHasPartialAccessTo($model, $query_params['caps'])) {
341 341
             $model_name_plural = EEH_Inflector::pluralize_and_lower($model->get_this_model_name());
342 342
             throw new RestException(
343 343
                 sprintf('rest_%s_cannot_list', $model_name_plural),
@@ -349,14 +349,14 @@  discard block
 block discarded – undo
349 349
                 array('status' => 403)
350 350
             );
351 351
         }
352
-        if (! $request->get_header('no_rest_headers')) {
352
+        if ( ! $request->get_header('no_rest_headers')) {
353 353
             $this->setHeadersFromQueryParams($model, $query_params);
354 354
         }
355 355
         /** @type array $results */
356 356
         $results = $model->get_all_wpdb_results($query_params);
357 357
         $nice_results = array();
358 358
         foreach ($results as $result) {
359
-            $nice_results[] =  $this->createEntityFromWpdbResult(
359
+            $nice_results[] = $this->createEntityFromWpdbResult(
360 360
                 $model,
361 361
                 $result,
362 362
                 $request
@@ -390,7 +390,7 @@  discard block
 block discarded – undo
390 390
         $context = $this->validateContext($request->get_param('caps'));
391 391
         $model = $relation->get_this_model();
392 392
         $related_model = $relation->get_other_model();
393
-        if (! isset($primary_model_query_params[0])) {
393
+        if ( ! isset($primary_model_query_params[0])) {
394 394
             $primary_model_query_params[0] = array();
395 395
         }
396 396
         // check if they can access the 1st model object
@@ -413,7 +413,7 @@  discard block
 block discarded – undo
413 413
         if (is_array($primary_model_rows)) {
414 414
             $primary_model_row = reset($primary_model_rows);
415 415
         }
416
-        if (! (
416
+        if ( ! (
417 417
             Capabilities::currentUserHasPartialAccessTo($related_model, $context)
418 418
             && $primary_model_row
419 419
         )
@@ -453,13 +453,13 @@  discard block
 block discarded – undo
453 453
         );
454 454
         $query_params = $this->createModelQueryParams($relation->get_other_model(), $request->get_params());
455 455
         foreach ($primary_model_query_params[0] as $where_condition_key => $where_condition_value) {
456
-            $query_params[0][ $relation->get_this_model()->get_this_model_name()
456
+            $query_params[0][$relation->get_this_model()->get_this_model_name()
457 457
                               . '.'
458
-                              . $where_condition_key ] = $where_condition_value;
458
+                              . $where_condition_key] = $where_condition_value;
459 459
         }
460 460
         $query_params['default_where_conditions'] = 'none';
461 461
         $query_params['caps'] = $context;
462
-        if (! $request->get_header('no_rest_headers')) {
462
+        if ( ! $request->get_header('no_rest_headers')) {
463 463
             $this->setHeadersFromQueryParams($relation->get_other_model(), $query_params);
464 464
         }
465 465
         /** @type array $results */
@@ -510,7 +510,7 @@  discard block
 block discarded – undo
510 510
      */
511 511
     public function getEntitiesFromRelation($id, $relation, $request)
512 512
     {
513
-        if (! $relation->get_this_model()->has_primary_key_field()) {
513
+        if ( ! $relation->get_this_model()->has_primary_key_field()) {
514 514
             throw new EE_Error(
515 515
                 sprintf(
516 516
                     __(
@@ -555,7 +555,7 @@  discard block
 block discarded – undo
555 555
             Capabilities::getMissingPermissionsString($model, $query_params['caps'])
556 556
         );
557 557
         // normally the limit to a 2-part array, where the 2nd item is the limit
558
-        if (! isset($query_params['limit'])) {
558
+        if ( ! isset($query_params['limit'])) {
559 559
             $query_params['limit'] = EED_Core_Rest_Api::get_default_query_limit();
560 560
         }
561 561
         if (is_array($query_params['limit'])) {
@@ -594,7 +594,7 @@  discard block
 block discarded – undo
594 594
      */
595 595
     public function createEntityFromWpdbResult($model, $db_row, $rest_request, $deprecated = null)
596 596
     {
597
-        if (! $rest_request instanceof WP_REST_Request) {
597
+        if ( ! $rest_request instanceof WP_REST_Request) {
598 598
             // ok so this was called in the old style, where the 3rd arg was
599 599
             // $include, and the 4th arg was $context
600 600
             // now setup the request just to avoid fatal errors, although we won't be able
@@ -669,7 +669,7 @@  discard block
 block discarded – undo
669 669
             $rest_request,
670 670
             $this
671 671
         );
672
-        if (! $current_user_full_access_to_entity) {
672
+        if ( ! $current_user_full_access_to_entity) {
673 673
             $result_without_inaccessible_fields = Capabilities::filterOutInaccessibleEntityFields(
674 674
                 $entity_array,
675 675
                 $model,
@@ -701,7 +701,7 @@  discard block
 block discarded – undo
701 701
      */
702 702
     protected function addProtectedProperty(EEM_Base $model, $results_so_far, $protected)
703 703
     {
704
-        if (! $model->hasPassword() || ! $protected) {
704
+        if ( ! $model->hasPassword() || ! $protected) {
705 705
             return $results_so_far;
706 706
         }
707 707
         $password_field = $model->getPasswordField();
@@ -715,7 +715,7 @@  discard block
 block discarded – undo
715 715
             $fields_included
716 716
         );
717 717
         foreach ($fields_included as $field_name) {
718
-            $results_so_far['_protected'][] = $field_name ;
718
+            $results_so_far['_protected'][] = $field_name;
719 719
         }
720 720
         return $results_so_far;
721 721
     }
@@ -746,8 +746,8 @@  discard block
 block discarded – undo
746 746
         if ($do_chevy_shuffle) {
747 747
             global $post;
748 748
             $old_post = $post;
749
-            $post = get_post($result[ $model->primary_key_name() ]);
750
-            if (! $post instanceof \WP_Post) {
749
+            $post = get_post($result[$model->primary_key_name()]);
750
+            if ( ! $post instanceof \WP_Post) {
751 751
                 // well that's weird, because $result is what we JUST fetched from the database
752 752
                 throw new RestException(
753 753
                     'error_fetching_post_from_database_results',
@@ -757,7 +757,7 @@  discard block
 block discarded – undo
757 757
                     )
758 758
                 );
759 759
             }
760
-            $model_object_classname = 'EE_' . $model->get_this_model_name();
760
+            $model_object_classname = 'EE_'.$model->get_this_model_name();
761 761
             $post->{$model_object_classname} = \EE_Registry::instance()->load_class(
762 762
                 $model_object_classname,
763 763
                 $result,
@@ -768,13 +768,13 @@  discard block
 block discarded – undo
768 768
         foreach ($result as $field_name => $field_value) {
769 769
             $field_obj = $model->field_settings_for($field_name);
770 770
             if ($this->isSubclassOfOne($field_obj, $this->getModelVersionInfo()->fieldsIgnored())) {
771
-                unset($result[ $field_name ]);
771
+                unset($result[$field_name]);
772 772
             } elseif ($this->isSubclassOfOne(
773 773
                 $field_obj,
774 774
                 $this->getModelVersionInfo()->fieldsThatHaveRenderedFormat()
775 775
             )
776 776
             ) {
777
-                $result[ $field_name ] = array(
777
+                $result[$field_name] = array(
778 778
                     'raw'      => $this->prepareFieldObjValueForJson($field_obj, $field_value),
779 779
                     'rendered' => $this->prepareFieldObjValueForJson($field_obj, $field_value, 'pretty'),
780 780
                 );
@@ -783,7 +783,7 @@  discard block
 block discarded – undo
783 783
                 $this->getModelVersionInfo()->fieldsThatHavePrettyFormat()
784 784
             )
785 785
             ) {
786
-                $result[ $field_name ] = array(
786
+                $result[$field_name] = array(
787 787
                     'raw'    => $this->prepareFieldObjValueForJson($field_obj, $field_value),
788 788
                     'pretty' => $this->prepareFieldObjValueForJson($field_obj, $field_value, 'pretty'),
789 789
                 );
@@ -814,10 +814,10 @@  discard block
 block discarded – undo
814 814
                         $this->getModelVersionInfo()->requestedVersion()
815 815
                     );
816 816
                 }
817
-                $result[ $field_name . '_gmt' ] = $gmt_date;
818
-                $result[ $field_name ] = $local_date;
817
+                $result[$field_name.'_gmt'] = $gmt_date;
818
+                $result[$field_name] = $local_date;
819 819
             } else {
820
-                $result[ $field_name ] = $this->prepareFieldObjValueForJson($field_obj, $field_value);
820
+                $result[$field_name] = $this->prepareFieldObjValueForJson($field_obj, $field_value);
821 821
             }
822 822
         }
823 823
         if ($do_chevy_shuffle) {
@@ -869,7 +869,7 @@  discard block
 block discarded – undo
869 869
     protected function addExtraFields(EEM_Base $model, $db_row, $entity_array)
870 870
     {
871 871
         if ($model instanceof EEM_CPT_Base) {
872
-            $entity_array['link'] = get_permalink($db_row[ $model->get_primary_key_field()->get_qualified_column() ]);
872
+            $entity_array['link'] = get_permalink($db_row[$model->get_primary_key_field()->get_qualified_column()]);
873 873
         }
874 874
         return $entity_array;
875 875
     }
@@ -894,7 +894,7 @@  discard block
 block discarded – undo
894 894
                     'href' => $this->getVersionedLinkTo(
895 895
                         EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
896 896
                         . '/'
897
-                        . $entity_array[ $model->primary_key_name() ]
897
+                        . $entity_array[$model->primary_key_name()]
898 898
                     ),
899 899
                 ),
900 900
             );
@@ -910,12 +910,12 @@  discard block
 block discarded – undo
910 910
         if ($model->has_primary_key_field()) {
911 911
             foreach ($this->getModelVersionInfo()->relationSettings($model) as $relation_name => $relation_obj) {
912 912
                 $related_model_part = Read::getRelatedEntityName($relation_name, $relation_obj);
913
-                $links[ EED_Core_Rest_Api::ee_api_link_namespace . $related_model_part ] = array(
913
+                $links[EED_Core_Rest_Api::ee_api_link_namespace.$related_model_part] = array(
914 914
                     array(
915 915
                         'href'   => $this->getVersionedLinkTo(
916 916
                             EEH_Inflector::pluralize_and_lower($model->get_this_model_name())
917 917
                             . '/'
918
-                            . $entity_array[ $model->primary_key_name() ]
918
+                            . $entity_array[$model->primary_key_name()]
919 919
                             . '/'
920 920
                             . $related_model_part
921 921
                         ),
@@ -947,7 +947,7 @@  discard block
 block discarded – undo
947 947
         $included_items_protected = false
948 948
     ) {
949 949
         // if $db_row not included, hope the entity array has what we need
950
-        if (! $db_row) {
950
+        if ( ! $db_row) {
951 951
             $db_row = $entity_array;
952 952
         }
953 953
         $relation_settings = $this->getModelVersionInfo()->relationSettings($model);
@@ -980,7 +980,7 @@  discard block
 block discarded – undo
980 980
                         $model->deduce_fields_n_values_from_cols_n_values($db_row)
981 981
                     )
982 982
                 );
983
-                if (! $included_items_protected) {
983
+                if ( ! $included_items_protected) {
984 984
                     try {
985 985
                         $related_results = $this->getEntitiesFromRelationUsingModelQueryParams(
986 986
                             $primary_model_query_params,
@@ -998,7 +998,7 @@  discard block
 block discarded – undo
998 998
                 if ($related_results instanceof WP_Error || $related_results === null) {
999 999
                     $related_results = $relation_obj instanceof EE_Belongs_To_Relation ? null : array();
1000 1000
                 }
1001
-                $entity_array[ Read::getRelatedEntityName($relation_name, $relation_obj) ] = $related_results;
1001
+                $entity_array[Read::getRelatedEntityName($relation_name, $relation_obj)] = $related_results;
1002 1002
             }
1003 1003
         }
1004 1004
         return $entity_array;
@@ -1023,7 +1023,7 @@  discard block
 block discarded – undo
1023 1023
         $includes_for_this_model = $this->explodeAndGetItemsPrefixedWith($rest_request->get_param('include'), '');
1024 1024
         $includes_for_this_model = $this->removeModelNamesFromArray($includes_for_this_model);
1025 1025
         // if they passed in * or didn't specify any includes, return everything
1026
-        if (! in_array('*', $includes_for_this_model)
1026
+        if ( ! in_array('*', $includes_for_this_model)
1027 1027
             && ! empty($includes_for_this_model)
1028 1028
         ) {
1029 1029
             if ($model->has_primary_key_field()) {
@@ -1077,12 +1077,12 @@  discard block
 block discarded – undo
1077 1077
                 // it's password protected, so they shouldn't be able to read this. Remove the value
1078 1078
                 $schema = $this->fields_calculator->getJsonSchemaForModel($model);
1079 1079
                 if ($row_is_protected
1080
-                    && isset($schema['properties'][ $field_to_calculate ]['protected'])
1081
-                    && $schema['properties'][ $field_to_calculate ]['protected']) {
1080
+                    && isset($schema['properties'][$field_to_calculate]['protected'])
1081
+                    && $schema['properties'][$field_to_calculate]['protected']) {
1082 1082
                     $calculated_value = null;
1083 1083
                     $protected_fields[] = $field_to_calculate;
1084
-                    if ($schema['properties'][ $field_to_calculate ]['type']) {
1085
-                        switch ($schema['properties'][ $field_to_calculate ]['type']) {
1084
+                    if ($schema['properties'][$field_to_calculate]['type']) {
1085
+                        switch ($schema['properties'][$field_to_calculate]['type']) {
1086 1086
                             case 'boolean':
1087 1087
                                 $calculated_value = false;
1088 1088
                                 break;
@@ -1192,7 +1192,7 @@  discard block
 block discarded – undo
1192 1192
      */
1193 1193
     public function validateContext($context)
1194 1194
     {
1195
-        if (! $context) {
1195
+        if ( ! $context) {
1196 1196
             $context = EEM_Base::caps_read;
1197 1197
         }
1198 1198
         $valid_contexts = EEM_Base::valid_cap_contexts();
@@ -1217,7 +1217,7 @@  discard block
 block discarded – undo
1217 1217
             EEM_Base::default_where_conditions_minimum_all,
1218 1218
             EEM_Base::default_where_conditions_minimum_others,
1219 1219
         );
1220
-        if (! $default_query_params) {
1220
+        if ( ! $default_query_params) {
1221 1221
             $default_query_params = EEM_Base::default_where_conditions_all;
1222 1222
         }
1223 1223
         if (in_array(
@@ -1300,14 +1300,14 @@  discard block
 block discarded – undo
1300 1300
         }
1301 1301
         if (isset($query_params['limit'])) {
1302 1302
             // limit should be either a string like '23' or '23,43', or an array with two items in it
1303
-            if (! is_array($query_params['limit'])) {
1303
+            if ( ! is_array($query_params['limit'])) {
1304 1304
                 $limit_array = explode(',', (string) $query_params['limit']);
1305 1305
             } else {
1306 1306
                 $limit_array = $query_params['limit'];
1307 1307
             }
1308 1308
             $sanitized_limit = array();
1309 1309
             foreach ($limit_array as $key => $limit_part) {
1310
-                if ($this->debug_mode && (! is_numeric($limit_part) || count($sanitized_limit) > 2)) {
1310
+                if ($this->debug_mode && ( ! is_numeric($limit_part) || count($sanitized_limit) > 2)) {
1311 1311
                     throw new EE_Error(
1312 1312
                         sprintf(
1313 1313
                             __(
@@ -1339,7 +1339,7 @@  discard block
 block discarded – undo
1339 1339
         // if this is a model protected by a password on another model, exclude the password protected
1340 1340
         // entities by default. But if they passed in a password, try to show them all. If the password is wrong,
1341 1341
         // though, they'll get an error (see Read::createEntityFromWpdbResult() which calls Read::checkPassword)
1342
-        if (! $model->hasPassword()
1342
+        if ( ! $model->hasPassword()
1343 1343
             && $model->restrictedByRelatedModelPassword()
1344 1344
             && $model_query_params['caps'] === EEM_Base::caps_read) {
1345 1345
             if (empty($query_params['password'])) {
@@ -1364,9 +1364,9 @@  discard block
 block discarded – undo
1364 1364
         $model_ready_query_params = array();
1365 1365
         foreach ($query_params as $key => $value) {
1366 1366
             if (is_array($value)) {
1367
-                $model_ready_query_params[ $key ] = $this->prepareRestQueryParamsKeyForModels($model, $value);
1367
+                $model_ready_query_params[$key] = $this->prepareRestQueryParamsKeyForModels($model, $value);
1368 1368
             } else {
1369
-                $model_ready_query_params[ $key ] = $value;
1369
+                $model_ready_query_params[$key] = $value;
1370 1370
             }
1371 1371
         }
1372 1372
         return $model_ready_query_params;
@@ -1384,9 +1384,9 @@  discard block
 block discarded – undo
1384 1384
         $model_ready_query_params = array();
1385 1385
         foreach ($query_params as $key => $value) {
1386 1386
             if (is_array($value)) {
1387
-                $model_ready_query_params[ $key ] = $this->prepareRestQueryParamsValuesForModels($model, $value);
1387
+                $model_ready_query_params[$key] = $this->prepareRestQueryParamsValuesForModels($model, $value);
1388 1388
             } else {
1389
-                $model_ready_query_params[ $key ] = $value;
1389
+                $model_ready_query_params[$key] = $value;
1390 1390
             }
1391 1391
         }
1392 1392
         return $model_ready_query_params;
@@ -1418,17 +1418,17 @@  discard block
 block discarded – undo
1418 1418
         foreach ($exploded_contents as $item) {
1419 1419
             $item = trim($item);
1420 1420
             // if no prefix was provided, so we look for items with no "." in them
1421
-            if (! $prefix) {
1421
+            if ( ! $prefix) {
1422 1422
                 // does this item have a period?
1423 1423
                 if (strpos($item, '.') === false) {
1424 1424
                     // if not, then its what we're looking for
1425 1425
                     $contents_with_prefix[] = $item;
1426 1426
                 }
1427
-            } elseif (strpos($item, $prefix . '.') === 0) {
1427
+            } elseif (strpos($item, $prefix.'.') === 0) {
1428 1428
                 // this item has the prefix and a period, grab it
1429 1429
                 $contents_with_prefix[] = substr(
1430 1430
                     $item,
1431
-                    strpos($item, $prefix . '.') + strlen($prefix . '.')
1431
+                    strpos($item, $prefix.'.') + strlen($prefix.'.')
1432 1432
                 );
1433 1433
             } elseif ($item === $prefix) {
1434 1434
                 // this item is JUST the prefix
@@ -1467,9 +1467,9 @@  discard block
 block discarded – undo
1467 1467
         if ($model_name) {
1468 1468
             foreach ($includes as $field_to_include) {
1469 1469
                 $field_to_include = trim($field_to_include);
1470
-                if (strpos($field_to_include, $model_name . '.') === 0) {
1470
+                if (strpos($field_to_include, $model_name.'.') === 0) {
1471 1471
                     // found the model name at the exact start
1472
-                    $field_sans_model_name = str_replace($model_name . '.', '', $field_to_include);
1472
+                    $field_sans_model_name = str_replace($model_name.'.', '', $field_to_include);
1473 1473
                     $extracted_fields_to_include[] = $field_sans_model_name;
1474 1474
                 } elseif ($field_to_include == $model_name) {
1475 1475
                     $extracted_fields_to_include[] = '*';
@@ -1510,7 +1510,7 @@  discard block
 block discarded – undo
1510 1510
         $restricted_query_params['caps'] = $context;
1511 1511
         $this->setDebugInfo('model query params', $restricted_query_params);
1512 1512
         $model_rows = $model->get_all_wpdb_results($restricted_query_params);
1513
-        if (! empty($model_rows)) {
1513
+        if ( ! empty($model_rows)) {
1514 1514
             return $this->createEntityFromWpdbResult(
1515 1515
                 $model,
1516 1516
                 reset($model_rows),
@@ -1522,7 +1522,7 @@  discard block
 block discarded – undo
1522 1522
             if ($model->exists($query_params)) {
1523 1523
                 // you got shafted- it existed but we didn't want to tell you!
1524 1524
                 throw new RestException(
1525
-                    'rest_user_cannot_' . $context,
1525
+                    'rest_user_cannot_'.$context,
1526 1526
                     sprintf(
1527 1527
                         __('Sorry, you cannot %1$s this %2$s. Missing permissions are: %3$s', 'event_espresso'),
1528 1528
                         $context,
@@ -1573,11 +1573,11 @@  discard block
 block discarded – undo
1573 1573
         }
1574 1574
         // if this entity requires a password, they better give it and it better be right!
1575 1575
         if ($model->hasPassword()
1576
-            && $model_row[ $model->getPasswordField()->get_qualified_column() ] !== '') {
1576
+            && $model_row[$model->getPasswordField()->get_qualified_column()] !== '') {
1577 1577
             if (empty($request['password'])) {
1578 1578
                 throw new RestPasswordRequiredException();
1579
-            } elseif (!hash_equals(
1580
-                $model_row[ $model->getPasswordField()->get_qualified_column() ],
1579
+            } elseif ( ! hash_equals(
1580
+                $model_row[$model->getPasswordField()->get_qualified_column()],
1581 1581
                 $request['password']
1582 1582
             )) {
1583 1583
                 throw new RestPasswordIncorrectException();
@@ -1588,12 +1588,12 @@  discard block
 block discarded – undo
1588 1588
             $password_supplied = $request->get_param('password');
1589 1589
             if (empty($password_supplied)) {
1590 1590
                 $query_params['exclude_protected'] = true;
1591
-                if (!$model->exists($query_params)) {
1591
+                if ( ! $model->exists($query_params)) {
1592 1592
                     throw new RestPasswordRequiredException();
1593 1593
                 }
1594 1594
             } else {
1595
-                $query_params[0][ $model->modelChainAndPassword() ] = $password_supplied;
1596
-                if (!$model->exists($query_params)) {
1595
+                $query_params[0][$model->modelChainAndPassword()] = $password_supplied;
1596
+                if ( ! $model->exists($query_params)) {
1597 1597
                     throw new RestPasswordIncorrectException();
1598 1598
                 }
1599 1599
             }
Please login to merge, or discard this patch.
espresso.php 1 patch
Indentation   +80 added lines, -80 removed lines patch added patch discarded remove patch
@@ -38,103 +38,103 @@
 block discarded – undo
38 38
  * @since           4.0
39 39
  */
40 40
 if (function_exists('espresso_version')) {
41
-    if (! function_exists('espresso_duplicate_plugin_error')) {
42
-        /**
43
-         *    espresso_duplicate_plugin_error
44
-         *    displays if more than one version of EE is activated at the same time
45
-         */
46
-        function espresso_duplicate_plugin_error()
47
-        {
48
-            ?>
41
+	if (! function_exists('espresso_duplicate_plugin_error')) {
42
+		/**
43
+		 *    espresso_duplicate_plugin_error
44
+		 *    displays if more than one version of EE is activated at the same time
45
+		 */
46
+		function espresso_duplicate_plugin_error()
47
+		{
48
+			?>
49 49
             <div class="error">
50 50
                 <p>
51 51
                     <?php
52
-                    echo esc_html__(
53
-                        'Can not run multiple versions of Event Espresso! One version has been automatically deactivated. Please verify that you have the correct version you want still active.',
54
-                        'event_espresso'
55
-                    ); ?>
52
+					echo esc_html__(
53
+						'Can not run multiple versions of Event Espresso! One version has been automatically deactivated. Please verify that you have the correct version you want still active.',
54
+						'event_espresso'
55
+					); ?>
56 56
                 </p>
57 57
             </div>
58 58
             <?php
59
-            espresso_deactivate_plugin(plugin_basename(__FILE__));
60
-        }
61
-    }
62
-    add_action('admin_notices', 'espresso_duplicate_plugin_error', 1);
59
+			espresso_deactivate_plugin(plugin_basename(__FILE__));
60
+		}
61
+	}
62
+	add_action('admin_notices', 'espresso_duplicate_plugin_error', 1);
63 63
 } else {
64
-    define('EE_MIN_PHP_VER_REQUIRED', '5.4.0');
65
-    if (! version_compare(PHP_VERSION, EE_MIN_PHP_VER_REQUIRED, '>=')) {
66
-        /**
67
-         * espresso_minimum_php_version_error
68
-         *
69
-         * @return void
70
-         */
71
-        function espresso_minimum_php_version_error()
72
-        {
73
-            ?>
64
+	define('EE_MIN_PHP_VER_REQUIRED', '5.4.0');
65
+	if (! version_compare(PHP_VERSION, EE_MIN_PHP_VER_REQUIRED, '>=')) {
66
+		/**
67
+		 * espresso_minimum_php_version_error
68
+		 *
69
+		 * @return void
70
+		 */
71
+		function espresso_minimum_php_version_error()
72
+		{
73
+			?>
74 74
             <div class="error">
75 75
                 <p>
76 76
                     <?php
77
-                    printf(
78
-                        esc_html__(
79
-                            'We\'re sorry, but Event Espresso requires PHP version %1$s or greater in order to operate. You are currently running version %2$s.%3$sIn order to update your version of PHP, you will need to contact your current hosting provider.%3$sFor information on stable PHP versions, please go to %4$s.',
80
-                            'event_espresso'
81
-                        ),
82
-                        EE_MIN_PHP_VER_REQUIRED,
83
-                        PHP_VERSION,
84
-                        '<br/>',
85
-                        '<a href="http://php.net/downloads.php">http://php.net/downloads.php</a>'
86
-                    );
87
-                    ?>
77
+					printf(
78
+						esc_html__(
79
+							'We\'re sorry, but Event Espresso requires PHP version %1$s or greater in order to operate. You are currently running version %2$s.%3$sIn order to update your version of PHP, you will need to contact your current hosting provider.%3$sFor information on stable PHP versions, please go to %4$s.',
80
+							'event_espresso'
81
+						),
82
+						EE_MIN_PHP_VER_REQUIRED,
83
+						PHP_VERSION,
84
+						'<br/>',
85
+						'<a href="http://php.net/downloads.php">http://php.net/downloads.php</a>'
86
+					);
87
+					?>
88 88
                 </p>
89 89
             </div>
90 90
             <?php
91
-            espresso_deactivate_plugin(plugin_basename(__FILE__));
92
-        }
91
+			espresso_deactivate_plugin(plugin_basename(__FILE__));
92
+		}
93 93
 
94
-        add_action('admin_notices', 'espresso_minimum_php_version_error', 1);
95
-    } else {
96
-        define('EVENT_ESPRESSO_MAIN_FILE', __FILE__);
97
-        /**
98
-         * espresso_version
99
-         * Returns the plugin version
100
-         *
101
-         * @return string
102
-         */
103
-        function espresso_version()
104
-        {
105
-            return apply_filters('FHEE__espresso__espresso_version', '4.9.77.rc.002');
106
-        }
94
+		add_action('admin_notices', 'espresso_minimum_php_version_error', 1);
95
+	} else {
96
+		define('EVENT_ESPRESSO_MAIN_FILE', __FILE__);
97
+		/**
98
+		 * espresso_version
99
+		 * Returns the plugin version
100
+		 *
101
+		 * @return string
102
+		 */
103
+		function espresso_version()
104
+		{
105
+			return apply_filters('FHEE__espresso__espresso_version', '4.9.77.rc.002');
106
+		}
107 107
 
108
-        /**
109
-         * espresso_plugin_activation
110
-         * adds a wp-option to indicate that EE has been activated via the WP admin plugins page
111
-         */
112
-        function espresso_plugin_activation()
113
-        {
114
-            update_option('ee_espresso_activation', true);
115
-        }
108
+		/**
109
+		 * espresso_plugin_activation
110
+		 * adds a wp-option to indicate that EE has been activated via the WP admin plugins page
111
+		 */
112
+		function espresso_plugin_activation()
113
+		{
114
+			update_option('ee_espresso_activation', true);
115
+		}
116 116
 
117
-        register_activation_hook(EVENT_ESPRESSO_MAIN_FILE, 'espresso_plugin_activation');
117
+		register_activation_hook(EVENT_ESPRESSO_MAIN_FILE, 'espresso_plugin_activation');
118 118
 
119
-        require_once __DIR__ . '/core/bootstrap_espresso.php';
120
-        bootstrap_espresso();
121
-    }
119
+		require_once __DIR__ . '/core/bootstrap_espresso.php';
120
+		bootstrap_espresso();
121
+	}
122 122
 }
123 123
 if (! function_exists('espresso_deactivate_plugin')) {
124
-    /**
125
-     *    deactivate_plugin
126
-     * usage:  espresso_deactivate_plugin( plugin_basename( __FILE__ ));
127
-     *
128
-     * @access public
129
-     * @param string $plugin_basename - the results of plugin_basename( __FILE__ ) for the plugin's main file
130
-     * @return    void
131
-     */
132
-    function espresso_deactivate_plugin($plugin_basename = '')
133
-    {
134
-        if (! function_exists('deactivate_plugins')) {
135
-            require_once ABSPATH . 'wp-admin/includes/plugin.php';
136
-        }
137
-        unset($_GET['activate'], $_REQUEST['activate']);
138
-        deactivate_plugins($plugin_basename);
139
-    }
124
+	/**
125
+	 *    deactivate_plugin
126
+	 * usage:  espresso_deactivate_plugin( plugin_basename( __FILE__ ));
127
+	 *
128
+	 * @access public
129
+	 * @param string $plugin_basename - the results of plugin_basename( __FILE__ ) for the plugin's main file
130
+	 * @return    void
131
+	 */
132
+	function espresso_deactivate_plugin($plugin_basename = '')
133
+	{
134
+		if (! function_exists('deactivate_plugins')) {
135
+			require_once ABSPATH . 'wp-admin/includes/plugin.php';
136
+		}
137
+		unset($_GET['activate'], $_REQUEST['activate']);
138
+		deactivate_plugins($plugin_basename);
139
+	}
140 140
 }
Please login to merge, or discard this patch.
core/db_classes/EE_Transaction.class.php 2 patches
Doc Comments   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -366,7 +366,7 @@  discard block
 block discarded – undo
366 366
 
367 367
 
368 368
     /**
369
-     * @return mixed|null
369
+     * @return EE_Cart|null
370 370
      * @throws EE_Error
371 371
      * @throws InvalidArgumentException
372 372
      * @throws InvalidDataTypeException
@@ -508,7 +508,7 @@  discard block
 block discarded – undo
508 508
      * Gets all the attendees for this transaction (handy for use with EE_Attendee's get_registrations_for_event
509 509
      * function for getting attendees and how many registrations they each have for an event)
510 510
      *
511
-     * @return mixed EE_Attendee[] by default, int if $output is set to 'COUNT'
511
+     * @return EE_Base_Class[] EE_Attendee[] by default, int if $output is set to 'COUNT'
512 512
      * @throws EE_Error
513 513
      * @throws InvalidArgumentException
514 514
      * @throws InvalidDataTypeException
@@ -564,7 +564,7 @@  discard block
 block discarded – undo
564 564
     /**
565 565
      * Gets all payments which have not been approved
566 566
      *
567
-     * @return EE_Base_Class[]|EEI_Payment[]
567
+     * @return EE_Base_Class[]
568 568
      * @throws EE_Error if a model is misconfigured somehow
569 569
      * @throws InvalidArgumentException
570 570
      * @throws InvalidDataTypeException
Please login to merge, or discard this patch.
Indentation   +1678 added lines, -1678 removed lines patch added patch discarded remove patch
@@ -13,1682 +13,1682 @@
 block discarded – undo
13 13
 class EE_Transaction extends EE_Base_Class implements EEI_Transaction
14 14
 {
15 15
 
16
-    /**
17
-     * The length of time in seconds that a lock is applied before being considered expired.
18
-     * It is not long because a transaction should only be locked for the duration of the request that locked it
19
-     */
20
-    const LOCK_EXPIRATION = 2;
21
-
22
-    /**
23
-     * txn status upon initial construction.
24
-     *
25
-     * @var string
26
-     */
27
-    protected $_old_txn_status;
28
-
29
-
30
-    /**
31
-     * @param array  $props_n_values          incoming values
32
-     * @param string $timezone                incoming timezone
33
-     *                                        (if not set the timezone set for the website will be used.)
34
-     * @param array  $date_formats            incoming date_formats in an array where the first value is the
35
-     *                                        date_format and the second value is the time format
36
-     * @return EE_Transaction
37
-     * @throws EE_Error
38
-     * @throws InvalidArgumentException
39
-     * @throws InvalidDataTypeException
40
-     * @throws InvalidInterfaceException
41
-     * @throws ReflectionException
42
-     */
43
-    public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
44
-    {
45
-        $has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
46
-        $txn = $has_object
47
-            ? $has_object
48
-            : new self($props_n_values, false, $timezone, $date_formats);
49
-        if (! $has_object) {
50
-            $txn->set_old_txn_status($txn->status_ID());
51
-        }
52
-        return $txn;
53
-    }
54
-
55
-
56
-    /**
57
-     * @param array  $props_n_values  incoming values from the database
58
-     * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
59
-     *                                the website will be used.
60
-     * @return EE_Transaction
61
-     * @throws EE_Error
62
-     * @throws InvalidArgumentException
63
-     * @throws InvalidDataTypeException
64
-     * @throws InvalidInterfaceException
65
-     * @throws ReflectionException
66
-     */
67
-    public static function new_instance_from_db($props_n_values = array(), $timezone = null)
68
-    {
69
-        $txn = new self($props_n_values, true, $timezone);
70
-        $txn->set_old_txn_status($txn->status_ID());
71
-        return $txn;
72
-    }
73
-
74
-
75
-    /**
76
-     * Sets a meta field indicating that this TXN is locked and should not be updated in the db.
77
-     * If a lock has already been set, then we will attempt to remove it in case it has expired.
78
-     * If that also fails, then an exception is thrown.
79
-     *
80
-     * @throws EE_Error
81
-     * @throws InvalidArgumentException
82
-     * @throws InvalidDataTypeException
83
-     * @throws InvalidInterfaceException
84
-     * @throws ReflectionException
85
-     */
86
-    public function lock()
87
-    {
88
-        // attempt to set lock, but if that fails...
89
-        if (! $this->add_extra_meta('lock', time(), true)) {
90
-            // then attempt to remove the lock in case it is expired
91
-            if ($this->_remove_expired_lock()) {
92
-                // if removal was successful, then try setting lock again
93
-                $this->lock();
94
-            } else {
95
-                // but if the lock can not be removed, then throw an exception
96
-                throw new EE_Error(
97
-                    sprintf(
98
-                        __(
99
-                            'Could not lock Transaction %1$d because it is already locked, meaning another part of the system is currently editing it. It should already be unlocked by the time you read this, so please refresh the page and try again.',
100
-                            'event_espresso'
101
-                        ),
102
-                        $this->ID()
103
-                    )
104
-                );
105
-            }
106
-        }
107
-    }
108
-
109
-
110
-    /**
111
-     * removes transaction lock applied in EE_Transaction::lock()
112
-     *
113
-     * @return int
114
-     * @throws EE_Error
115
-     * @throws InvalidArgumentException
116
-     * @throws InvalidDataTypeException
117
-     * @throws InvalidInterfaceException
118
-     * @throws ReflectionException
119
-     */
120
-    public function unlock()
121
-    {
122
-        return $this->delete_extra_meta('lock');
123
-    }
124
-
125
-
126
-    /**
127
-     * Decides whether or not now is the right time to update the transaction.
128
-     * This is useful because we don't always know if it is safe to update the transaction
129
-     * and its related data. why?
130
-     * because it's possible that the transaction is being used in another
131
-     * request and could overwrite anything we save.
132
-     * So we want to only update the txn once we know that won't happen.
133
-     * We also check that the lock isn't expired, and remove it if it is
134
-     *
135
-     * @return boolean
136
-     * @throws EE_Error
137
-     * @throws InvalidArgumentException
138
-     * @throws InvalidDataTypeException
139
-     * @throws InvalidInterfaceException
140
-     * @throws ReflectionException
141
-     */
142
-    public function is_locked()
143
-    {
144
-        // if TXN is not locked, then return false immediately
145
-        if (! $this->_get_lock()) {
146
-            return false;
147
-        }
148
-        // if not, then let's try and remove the lock in case it's expired...
149
-        // _remove_expired_lock() returns 0 when lock is valid (ie: removed = false)
150
-        // and a positive number if the lock was removed (ie: number of locks deleted),
151
-        // so we need to return the opposite
152
-        return ! $this->_remove_expired_lock() ? true : false;
153
-    }
154
-
155
-
156
-    /**
157
-     * Gets the meta field indicating that this TXN is locked
158
-     *
159
-     * @return int
160
-     * @throws EE_Error
161
-     * @throws InvalidArgumentException
162
-     * @throws InvalidDataTypeException
163
-     * @throws InvalidInterfaceException
164
-     * @throws ReflectionException
165
-     */
166
-    protected function _get_lock()
167
-    {
168
-        return (int) $this->get_extra_meta('lock', true, 0);
169
-    }
170
-
171
-
172
-    /**
173
-     * If the lock on this transaction is expired, then we want to remove it so that the transaction can be updated
174
-     *
175
-     * @return int
176
-     * @throws EE_Error
177
-     * @throws InvalidArgumentException
178
-     * @throws InvalidDataTypeException
179
-     * @throws InvalidInterfaceException
180
-     * @throws ReflectionException
181
-     */
182
-    protected function _remove_expired_lock()
183
-    {
184
-        $locked = $this->_get_lock();
185
-        if ($locked && time() - EE_Transaction::LOCK_EXPIRATION > $locked) {
186
-            return $this->unlock();
187
-        }
188
-        return 0;
189
-    }
190
-
191
-
192
-    /**
193
-     * Set transaction total
194
-     *
195
-     * @param float $total total value of transaction
196
-     * @throws EE_Error
197
-     * @throws InvalidArgumentException
198
-     * @throws InvalidDataTypeException
199
-     * @throws InvalidInterfaceException
200
-     * @throws ReflectionException
201
-     */
202
-    public function set_total($total = 0.00)
203
-    {
204
-        $this->set('TXN_total', (float) $total);
205
-    }
206
-
207
-
208
-    /**
209
-     * Set Total Amount Paid to Date
210
-     *
211
-     * @param float $total_paid total amount paid to date (sum of all payments)
212
-     * @throws EE_Error
213
-     * @throws InvalidArgumentException
214
-     * @throws InvalidDataTypeException
215
-     * @throws InvalidInterfaceException
216
-     * @throws ReflectionException
217
-     */
218
-    public function set_paid($total_paid = 0.00)
219
-    {
220
-        $this->set('TXN_paid', (float) $total_paid);
221
-    }
222
-
223
-
224
-    /**
225
-     * Set transaction status
226
-     *
227
-     * @param string $status        whether the transaction is open, declined, accepted,
228
-     *                              or any number of custom values that can be set
229
-     * @throws EE_Error
230
-     * @throws InvalidArgumentException
231
-     * @throws InvalidDataTypeException
232
-     * @throws InvalidInterfaceException
233
-     * @throws ReflectionException
234
-     */
235
-    public function set_status($status = '')
236
-    {
237
-        $this->set('STS_ID', $status);
238
-    }
239
-
240
-
241
-    /**
242
-     * Set hash salt
243
-     *
244
-     * @param string $hash_salt required for some payment gateways
245
-     * @throws EE_Error
246
-     * @throws InvalidArgumentException
247
-     * @throws InvalidDataTypeException
248
-     * @throws InvalidInterfaceException
249
-     * @throws ReflectionException
250
-     */
251
-    public function set_hash_salt($hash_salt = '')
252
-    {
253
-        $this->set('TXN_hash_salt', $hash_salt);
254
-    }
255
-
256
-
257
-    /**
258
-     * Sets TXN_reg_steps array
259
-     *
260
-     * @param array $txn_reg_steps
261
-     * @throws EE_Error
262
-     * @throws InvalidArgumentException
263
-     * @throws InvalidDataTypeException
264
-     * @throws InvalidInterfaceException
265
-     * @throws ReflectionException
266
-     */
267
-    public function set_reg_steps(array $txn_reg_steps)
268
-    {
269
-        $this->set('TXN_reg_steps', $txn_reg_steps);
270
-    }
271
-
272
-
273
-    /**
274
-     * Gets TXN_reg_steps
275
-     *
276
-     * @return array
277
-     * @throws EE_Error
278
-     * @throws InvalidArgumentException
279
-     * @throws InvalidDataTypeException
280
-     * @throws InvalidInterfaceException
281
-     * @throws ReflectionException
282
-     */
283
-    public function reg_steps()
284
-    {
285
-        $TXN_reg_steps = $this->get('TXN_reg_steps');
286
-        return is_array($TXN_reg_steps) ? (array) $TXN_reg_steps : array();
287
-    }
288
-
289
-
290
-    /**
291
-     * @return string of transaction's total cost, with currency symbol and decimal
292
-     * @throws EE_Error
293
-     * @throws InvalidArgumentException
294
-     * @throws InvalidDataTypeException
295
-     * @throws InvalidInterfaceException
296
-     * @throws ReflectionException
297
-     */
298
-    public function pretty_total()
299
-    {
300
-        return $this->get_pretty('TXN_total');
301
-    }
302
-
303
-
304
-    /**
305
-     * Gets the amount paid in a pretty string (formatted and with currency symbol)
306
-     *
307
-     * @return string
308
-     * @throws EE_Error
309
-     * @throws InvalidArgumentException
310
-     * @throws InvalidDataTypeException
311
-     * @throws InvalidInterfaceException
312
-     * @throws ReflectionException
313
-     */
314
-    public function pretty_paid()
315
-    {
316
-        return $this->get_pretty('TXN_paid');
317
-    }
318
-
319
-
320
-    /**
321
-     * calculate the amount remaining for this transaction and return;
322
-     *
323
-     * @return float amount remaining
324
-     * @throws EE_Error
325
-     * @throws InvalidArgumentException
326
-     * @throws InvalidDataTypeException
327
-     * @throws InvalidInterfaceException
328
-     * @throws ReflectionException
329
-     */
330
-    public function remaining()
331
-    {
332
-        return $this->total() - $this->paid();
333
-    }
334
-
335
-
336
-    /**
337
-     * get Transaction Total
338
-     *
339
-     * @return float
340
-     * @throws EE_Error
341
-     * @throws InvalidArgumentException
342
-     * @throws InvalidDataTypeException
343
-     * @throws InvalidInterfaceException
344
-     * @throws ReflectionException
345
-     */
346
-    public function total()
347
-    {
348
-        return (float) $this->get('TXN_total');
349
-    }
350
-
351
-
352
-    /**
353
-     * get Total Amount Paid to Date
354
-     *
355
-     * @return float
356
-     * @throws EE_Error
357
-     * @throws InvalidArgumentException
358
-     * @throws InvalidDataTypeException
359
-     * @throws InvalidInterfaceException
360
-     * @throws ReflectionException
361
-     */
362
-    public function paid()
363
-    {
364
-        return (float) $this->get('TXN_paid');
365
-    }
366
-
367
-
368
-    /**
369
-     * @return mixed|null
370
-     * @throws EE_Error
371
-     * @throws InvalidArgumentException
372
-     * @throws InvalidDataTypeException
373
-     * @throws InvalidInterfaceException
374
-     * @throws ReflectionException
375
-     */
376
-    public function get_cart_session()
377
-    {
378
-        $session_data = (array) $this->get('TXN_session_data');
379
-        return isset($session_data['cart']) && $session_data['cart'] instanceof EE_Cart
380
-            ? $session_data['cart']
381
-            : null;
382
-    }
383
-
384
-
385
-    /**
386
-     * get Transaction session data
387
-     *
388
-     * @return array|mixed
389
-     * @throws EE_Error
390
-     * @throws InvalidArgumentException
391
-     * @throws InvalidDataTypeException
392
-     * @throws InvalidInterfaceException
393
-     * @throws ReflectionException
394
-     */
395
-    public function session_data()
396
-    {
397
-        $session_data = $this->get('TXN_session_data');
398
-        if (empty($session_data)) {
399
-            $session_data = array(
400
-                'id'            => null,
401
-                'user_id'       => null,
402
-                'ip_address'    => null,
403
-                'user_agent'    => null,
404
-                'init_access'   => null,
405
-                'last_access'   => null,
406
-                'pages_visited' => array(),
407
-            );
408
-        }
409
-        return $session_data;
410
-    }
411
-
412
-
413
-    /**
414
-     * Set session data within the TXN object
415
-     *
416
-     * @param EE_Session|array $session_data
417
-     * @throws EE_Error
418
-     * @throws InvalidArgumentException
419
-     * @throws InvalidDataTypeException
420
-     * @throws InvalidInterfaceException
421
-     * @throws ReflectionException
422
-     */
423
-    public function set_txn_session_data($session_data)
424
-    {
425
-        if ($session_data instanceof EE_Session) {
426
-            $this->set('TXN_session_data', $session_data->get_session_data(null, true));
427
-        } else {
428
-            $this->set('TXN_session_data', $session_data);
429
-        }
430
-    }
431
-
432
-
433
-    /**
434
-     * get Transaction hash salt
435
-     *
436
-     * @return mixed
437
-     * @throws EE_Error
438
-     * @throws InvalidArgumentException
439
-     * @throws InvalidDataTypeException
440
-     * @throws InvalidInterfaceException
441
-     * @throws ReflectionException
442
-     */
443
-    public function hash_salt_()
444
-    {
445
-        return $this->get('TXN_hash_salt');
446
-    }
447
-
448
-
449
-    /**
450
-     * Returns the transaction datetime as either:
451
-     *            - unix timestamp format ($format = false, $gmt = true)
452
-     *            - formatted date string including the UTC (timezone) offset ($format = true ($gmt
453
-     *              has no affect with this option)), this also may include a timezone abbreviation if the
454
-     *              set timezone in this class differs from what the timezone is on the blog.
455
-     *            - formatted date string including the UTC (timezone) offset (default).
456
-     *
457
-     * @param boolean $format   - whether to return a unix timestamp (default) or formatted date string
458
-     * @param boolean $gmt      - whether to return a unix timestamp with UTC offset applied (default)
459
-     *                          or no UTC offset applied
460
-     * @return string | int
461
-     * @throws EE_Error
462
-     * @throws InvalidArgumentException
463
-     * @throws InvalidDataTypeException
464
-     * @throws InvalidInterfaceException
465
-     * @throws ReflectionException
466
-     */
467
-    public function datetime($format = false, $gmt = false)
468
-    {
469
-        if ($format) {
470
-            return $this->get_pretty('TXN_timestamp');
471
-        }
472
-        if ($gmt) {
473
-            return $this->get_raw('TXN_timestamp');
474
-        }
475
-        return $this->get('TXN_timestamp');
476
-    }
477
-
478
-
479
-    /**
480
-     * Gets registrations on this transaction
481
-     *
482
-     * @param array   $query_params array of query parameters
483
-     * @param boolean $get_cached   TRUE to retrieve cached registrations or FALSE to pull from the db
484
-     * @return EE_Base_Class[]|EE_Registration[]
485
-     * @throws EE_Error
486
-     * @throws InvalidArgumentException
487
-     * @throws InvalidDataTypeException
488
-     * @throws InvalidInterfaceException
489
-     * @throws ReflectionException
490
-     */
491
-    public function registrations($query_params = array(), $get_cached = false)
492
-    {
493
-        $query_params = (empty($query_params) || ! is_array($query_params))
494
-            ? array(
495
-                'order_by' => array(
496
-                    'Event.EVT_name'     => 'ASC',
497
-                    'Attendee.ATT_lname' => 'ASC',
498
-                    'Attendee.ATT_fname' => 'ASC',
499
-                ),
500
-            )
501
-            : $query_params;
502
-        $query_params = $get_cached ? array() : $query_params;
503
-        return $this->get_many_related('Registration', $query_params);
504
-    }
505
-
506
-
507
-    /**
508
-     * Gets all the attendees for this transaction (handy for use with EE_Attendee's get_registrations_for_event
509
-     * function for getting attendees and how many registrations they each have for an event)
510
-     *
511
-     * @return mixed EE_Attendee[] by default, int if $output is set to 'COUNT'
512
-     * @throws EE_Error
513
-     * @throws InvalidArgumentException
514
-     * @throws InvalidDataTypeException
515
-     * @throws InvalidInterfaceException
516
-     * @throws ReflectionException
517
-     */
518
-    public function attendees()
519
-    {
520
-        return $this->get_many_related('Attendee', array(array('Registration.Transaction.TXN_ID' => $this->ID())));
521
-    }
522
-
523
-
524
-    /**
525
-     * Gets payments for this transaction. Unlike other such functions, order by 'DESC' by default
526
-     *
527
-     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
528
-     * @return EE_Base_Class[]|EE_Payment[]
529
-     * @throws EE_Error
530
-     * @throws InvalidArgumentException
531
-     * @throws InvalidDataTypeException
532
-     * @throws InvalidInterfaceException
533
-     * @throws ReflectionException
534
-     */
535
-    public function payments($query_params = array())
536
-    {
537
-        return $this->get_many_related('Payment', $query_params);
538
-    }
539
-
540
-
541
-    /**
542
-     * gets only approved payments for this transaction
543
-     *
544
-     * @return EE_Base_Class[]|EE_Payment[]
545
-     * @throws EE_Error
546
-     * @throws InvalidArgumentException
547
-     * @throws ReflectionException
548
-     * @throws InvalidDataTypeException
549
-     * @throws InvalidInterfaceException
550
-     */
551
-    public function approved_payments()
552
-    {
553
-        EE_Registry::instance()->load_model('Payment');
554
-        return $this->get_many_related(
555
-            'Payment',
556
-            array(
557
-                array('STS_ID' => EEM_Payment::status_id_approved),
558
-                'order_by' => array('PAY_timestamp' => 'DESC'),
559
-            )
560
-        );
561
-    }
562
-
563
-
564
-    /**
565
-     * Gets all payments which have not been approved
566
-     *
567
-     * @return EE_Base_Class[]|EEI_Payment[]
568
-     * @throws EE_Error if a model is misconfigured somehow
569
-     * @throws InvalidArgumentException
570
-     * @throws InvalidDataTypeException
571
-     * @throws InvalidInterfaceException
572
-     * @throws ReflectionException
573
-     */
574
-    public function pending_payments()
575
-    {
576
-        return $this->get_many_related(
577
-            'Payment',
578
-            array(
579
-                array(
580
-                    'STS_ID' => EEM_Payment::status_id_pending,
581
-                ),
582
-                'order_by' => array(
583
-                    'PAY_timestamp' => 'DESC',
584
-                ),
585
-            )
586
-        );
587
-    }
588
-
589
-
590
-    /**
591
-     * echoes $this->pretty_status()
592
-     *
593
-     * @param bool $show_icons
594
-     * @throws EE_Error
595
-     * @throws InvalidArgumentException
596
-     * @throws InvalidDataTypeException
597
-     * @throws InvalidInterfaceException
598
-     * @throws ReflectionException
599
-     */
600
-    public function e_pretty_status($show_icons = false)
601
-    {
602
-        echo $this->pretty_status($show_icons);
603
-    }
604
-
605
-
606
-    /**
607
-     * returns a pretty version of the status, good for displaying to users
608
-     *
609
-     * @param bool $show_icons
610
-     * @return string
611
-     * @throws EE_Error
612
-     * @throws InvalidArgumentException
613
-     * @throws InvalidDataTypeException
614
-     * @throws InvalidInterfaceException
615
-     * @throws ReflectionException
616
-     */
617
-    public function pretty_status($show_icons = false)
618
-    {
619
-        $status = EEM_Status::instance()->localized_status(
620
-            array($this->status_ID() => __('unknown', 'event_espresso')),
621
-            false,
622
-            'sentence'
623
-        );
624
-        $icon = '';
625
-        switch ($this->status_ID()) {
626
-            case EEM_Transaction::complete_status_code:
627
-                $icon = $show_icons ? '<span class="dashicons dashicons-yes ee-icon-size-24 green-text"></span>' : '';
628
-                break;
629
-            case EEM_Transaction::incomplete_status_code:
630
-                $icon = $show_icons ? '<span class="dashicons dashicons-marker ee-icon-size-16 lt-blue-text"></span>'
631
-                    : '';
632
-                break;
633
-            case EEM_Transaction::abandoned_status_code:
634
-                $icon = $show_icons ? '<span class="dashicons dashicons-marker ee-icon-size-16 red-text"></span>' : '';
635
-                break;
636
-            case EEM_Transaction::failed_status_code:
637
-                $icon = $show_icons ? '<span class="dashicons dashicons-no ee-icon-size-16 red-text"></span>' : '';
638
-                break;
639
-            case EEM_Transaction::overpaid_status_code:
640
-                $icon = $show_icons ? '<span class="dashicons dashicons-plus ee-icon-size-16 orange-text"></span>' : '';
641
-                break;
642
-        }
643
-        return $icon . $status[ $this->status_ID() ];
644
-    }
645
-
646
-
647
-    /**
648
-     * get Transaction Status
649
-     *
650
-     * @return mixed
651
-     * @throws EE_Error
652
-     * @throws InvalidArgumentException
653
-     * @throws InvalidDataTypeException
654
-     * @throws InvalidInterfaceException
655
-     * @throws ReflectionException
656
-     */
657
-    public function status_ID()
658
-    {
659
-        return $this->get('STS_ID');
660
-    }
661
-
662
-
663
-    /**
664
-     * Returns TRUE or FALSE for whether or not this transaction cost any money
665
-     *
666
-     * @return boolean
667
-     * @throws EE_Error
668
-     * @throws InvalidArgumentException
669
-     * @throws InvalidDataTypeException
670
-     * @throws InvalidInterfaceException
671
-     * @throws ReflectionException
672
-     */
673
-    public function is_free()
674
-    {
675
-        return EEH_Money::compare_floats($this->get('TXN_total'), 0, '==');
676
-    }
677
-
678
-
679
-    /**
680
-     * Returns whether this transaction is complete
681
-     * Useful in templates and other logic for deciding if we should ask for another payment...
682
-     *
683
-     * @return boolean
684
-     * @throws EE_Error
685
-     * @throws InvalidArgumentException
686
-     * @throws InvalidDataTypeException
687
-     * @throws InvalidInterfaceException
688
-     * @throws ReflectionException
689
-     */
690
-    public function is_completed()
691
-    {
692
-        return $this->status_ID() === EEM_Transaction::complete_status_code;
693
-    }
694
-
695
-
696
-    /**
697
-     * Returns whether this transaction is incomplete
698
-     * Useful in templates and other logic for deciding if we should ask for another payment...
699
-     *
700
-     * @return boolean
701
-     * @throws EE_Error
702
-     * @throws InvalidArgumentException
703
-     * @throws InvalidDataTypeException
704
-     * @throws InvalidInterfaceException
705
-     * @throws ReflectionException
706
-     */
707
-    public function is_incomplete()
708
-    {
709
-        return $this->status_ID() === EEM_Transaction::incomplete_status_code;
710
-    }
711
-
712
-
713
-    /**
714
-     * Returns whether this transaction is overpaid
715
-     * Useful in templates and other logic for deciding if monies need to be refunded
716
-     *
717
-     * @return boolean
718
-     * @throws EE_Error
719
-     * @throws InvalidArgumentException
720
-     * @throws InvalidDataTypeException
721
-     * @throws InvalidInterfaceException
722
-     * @throws ReflectionException
723
-     */
724
-    public function is_overpaid()
725
-    {
726
-        return $this->status_ID() === EEM_Transaction::overpaid_status_code;
727
-    }
728
-
729
-
730
-    /**
731
-     * Returns whether this transaction was abandoned
732
-     * meaning that the transaction/registration process was somehow interrupted and never completed
733
-     * but that contact information exists for at least one registrant
734
-     *
735
-     * @return boolean
736
-     * @throws EE_Error
737
-     * @throws InvalidArgumentException
738
-     * @throws InvalidDataTypeException
739
-     * @throws InvalidInterfaceException
740
-     * @throws ReflectionException
741
-     */
742
-    public function is_abandoned()
743
-    {
744
-        return $this->status_ID() === EEM_Transaction::abandoned_status_code;
745
-    }
746
-
747
-
748
-    /**
749
-     * Returns whether this transaction failed
750
-     * meaning that the transaction/registration process was somehow interrupted and never completed
751
-     * and that NO contact information exists for any registrants
752
-     *
753
-     * @return boolean
754
-     * @throws EE_Error
755
-     * @throws InvalidArgumentException
756
-     * @throws InvalidDataTypeException
757
-     * @throws InvalidInterfaceException
758
-     * @throws ReflectionException
759
-     */
760
-    public function failed()
761
-    {
762
-        return $this->status_ID() === EEM_Transaction::failed_status_code;
763
-    }
764
-
765
-
766
-    /**
767
-     * This returns the url for the invoice of this transaction
768
-     *
769
-     * @param string $type 'html' or 'pdf' (default is pdf)
770
-     * @return string
771
-     * @throws EE_Error
772
-     * @throws InvalidArgumentException
773
-     * @throws InvalidDataTypeException
774
-     * @throws InvalidInterfaceException
775
-     * @throws ReflectionException
776
-     */
777
-    public function invoice_url($type = 'html')
778
-    {
779
-        $REG = $this->primary_registration();
780
-        if (! $REG instanceof EE_Registration) {
781
-            return '';
782
-        }
783
-        return $REG->invoice_url($type);
784
-    }
785
-
786
-
787
-    /**
788
-     * Gets the primary registration only
789
-     *
790
-     * @return EE_Base_Class|EE_Registration
791
-     * @throws EE_Error
792
-     * @throws InvalidArgumentException
793
-     * @throws InvalidDataTypeException
794
-     * @throws InvalidInterfaceException
795
-     * @throws ReflectionException
796
-     */
797
-    public function primary_registration()
798
-    {
799
-        $registrations = (array) $this->get_many_related(
800
-            'Registration',
801
-            array(array('REG_count' => EEM_Registration::PRIMARY_REGISTRANT_COUNT))
802
-        );
803
-        foreach ($registrations as $registration) {
804
-            // valid registration that is NOT cancelled or declined ?
805
-            if ($registration instanceof EE_Registration
806
-                && ! in_array($registration->status_ID(), EEM_Registration::closed_reg_statuses(), true)
807
-            ) {
808
-                return $registration;
809
-            }
810
-        }
811
-        // nothing valid found, so just return first thing from array of results
812
-        return reset($registrations);
813
-    }
814
-
815
-
816
-    /**
817
-     * Gets the URL for viewing the receipt
818
-     *
819
-     * @param string $type 'pdf' or 'html' (default is 'html')
820
-     * @return string
821
-     * @throws EE_Error
822
-     * @throws InvalidArgumentException
823
-     * @throws InvalidDataTypeException
824
-     * @throws InvalidInterfaceException
825
-     * @throws ReflectionException
826
-     */
827
-    public function receipt_url($type = 'html')
828
-    {
829
-        $REG = $this->primary_registration();
830
-        if (! $REG instanceof EE_Registration) {
831
-            return '';
832
-        }
833
-        return $REG->receipt_url($type);
834
-    }
835
-
836
-
837
-    /**
838
-     * Gets the URL of the thank you page with this registration REG_url_link added as
839
-     * a query parameter
840
-     *
841
-     * @return string
842
-     * @throws EE_Error
843
-     * @throws InvalidArgumentException
844
-     * @throws InvalidDataTypeException
845
-     * @throws InvalidInterfaceException
846
-     * @throws ReflectionException
847
-     */
848
-    public function payment_overview_url()
849
-    {
850
-        $primary_registration = $this->primary_registration();
851
-        return $primary_registration instanceof EE_Registration ? $primary_registration->payment_overview_url() : false;
852
-    }
853
-
854
-
855
-    /**
856
-     * @return string
857
-     * @throws EE_Error
858
-     * @throws InvalidArgumentException
859
-     * @throws InvalidDataTypeException
860
-     * @throws InvalidInterfaceException
861
-     * @throws ReflectionException
862
-     */
863
-    public function gateway_response_on_transaction()
864
-    {
865
-        $payment = $this->get_first_related('Payment');
866
-        return $payment instanceof EE_Payment ? $payment->gateway_response() : '';
867
-    }
868
-
869
-
870
-    /**
871
-     * Get the status object of this object
872
-     *
873
-     * @return EE_Base_Class|EE_Status
874
-     * @throws EE_Error
875
-     * @throws InvalidArgumentException
876
-     * @throws InvalidDataTypeException
877
-     * @throws InvalidInterfaceException
878
-     * @throws ReflectionException
879
-     */
880
-    public function status_obj()
881
-    {
882
-        return $this->get_first_related('Status');
883
-    }
884
-
885
-
886
-    /**
887
-     * Gets all the extra meta info on this payment
888
-     *
889
-     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
890
-     * @return EE_Base_Class[]|EE_Extra_Meta
891
-     * @throws EE_Error
892
-     * @throws InvalidArgumentException
893
-     * @throws InvalidDataTypeException
894
-     * @throws InvalidInterfaceException
895
-     * @throws ReflectionException
896
-     */
897
-    public function extra_meta($query_params = array())
898
-    {
899
-        return $this->get_many_related('Extra_Meta', $query_params);
900
-    }
901
-
902
-
903
-    /**
904
-     * Wrapper for _add_relation_to
905
-     *
906
-     * @param EE_Registration $registration
907
-     * @return EE_Base_Class the relation was added to
908
-     * @throws EE_Error
909
-     * @throws InvalidArgumentException
910
-     * @throws InvalidDataTypeException
911
-     * @throws InvalidInterfaceException
912
-     * @throws ReflectionException
913
-     */
914
-    public function add_registration(EE_Registration $registration)
915
-    {
916
-        return $this->_add_relation_to($registration, 'Registration');
917
-    }
918
-
919
-
920
-    /**
921
-     * Removes the given registration from being related (even before saving this transaction).
922
-     * If an ID/index is provided and this transaction isn't saved yet, removes it from list of cached relations
923
-     *
924
-     * @param int $registration_or_id
925
-     * @return EE_Base_Class that was removed from being related
926
-     * @throws EE_Error
927
-     * @throws InvalidArgumentException
928
-     * @throws InvalidDataTypeException
929
-     * @throws InvalidInterfaceException
930
-     * @throws ReflectionException
931
-     */
932
-    public function remove_registration_with_id($registration_or_id)
933
-    {
934
-        return $this->_remove_relation_to($registration_or_id, 'Registration');
935
-    }
936
-
937
-
938
-    /**
939
-     * Gets all the line items which are for ACTUAL items
940
-     *
941
-     * @return EE_Line_Item[]
942
-     * @throws EE_Error
943
-     * @throws InvalidArgumentException
944
-     * @throws InvalidDataTypeException
945
-     * @throws InvalidInterfaceException
946
-     * @throws ReflectionException
947
-     */
948
-    public function items_purchased()
949
-    {
950
-        return $this->line_items(array(array('LIN_type' => EEM_Line_Item::type_line_item)));
951
-    }
952
-
953
-
954
-    /**
955
-     * Wrapper for _add_relation_to
956
-     *
957
-     * @param EE_Line_Item $line_item
958
-     * @return EE_Base_Class the relation was added to
959
-     * @throws EE_Error
960
-     * @throws InvalidArgumentException
961
-     * @throws InvalidDataTypeException
962
-     * @throws InvalidInterfaceException
963
-     * @throws ReflectionException
964
-     */
965
-    public function add_line_item(EE_Line_Item $line_item)
966
-    {
967
-        return $this->_add_relation_to($line_item, 'Line_Item');
968
-    }
969
-
970
-
971
-    /**
972
-     * Gets ALL the line items related to this transaction (unstructured)
973
-     *
974
-     * @param array $query_params
975
-     * @return EE_Base_Class[]|EE_Line_Item[]
976
-     * @throws EE_Error
977
-     * @throws InvalidArgumentException
978
-     * @throws InvalidDataTypeException
979
-     * @throws InvalidInterfaceException
980
-     * @throws ReflectionException
981
-     */
982
-    public function line_items($query_params = array())
983
-    {
984
-        return $this->get_many_related('Line_Item', $query_params);
985
-    }
986
-
987
-
988
-    /**
989
-     * Gets all the line items which are taxes on the total
990
-     *
991
-     * @return EE_Line_Item[]
992
-     * @throws EE_Error
993
-     * @throws InvalidArgumentException
994
-     * @throws InvalidDataTypeException
995
-     * @throws InvalidInterfaceException
996
-     * @throws ReflectionException
997
-     */
998
-    public function tax_items()
999
-    {
1000
-        return $this->line_items(array(array('LIN_type' => EEM_Line_Item::type_tax)));
1001
-    }
1002
-
1003
-
1004
-    /**
1005
-     * Gets the total line item (which is a parent of all other related line items,
1006
-     * meaning it takes them all into account on its total)
1007
-     *
1008
-     * @param bool $create_if_not_found
1009
-     * @return \EE_Line_Item
1010
-     * @throws EE_Error
1011
-     * @throws InvalidArgumentException
1012
-     * @throws InvalidDataTypeException
1013
-     * @throws InvalidInterfaceException
1014
-     * @throws ReflectionException
1015
-     */
1016
-    public function total_line_item($create_if_not_found = true)
1017
-    {
1018
-        $item = $this->get_first_related('Line_Item', array(array('LIN_type' => EEM_Line_Item::type_total)));
1019
-        if (! $item && $create_if_not_found) {
1020
-            $item = EEH_Line_Item::create_total_line_item($this);
1021
-        }
1022
-        return $item;
1023
-    }
1024
-
1025
-
1026
-    /**
1027
-     * Returns the total amount of tax on this transaction
1028
-     * (assumes there's only one tax subtotal line item)
1029
-     *
1030
-     * @return float
1031
-     * @throws EE_Error
1032
-     * @throws InvalidArgumentException
1033
-     * @throws InvalidDataTypeException
1034
-     * @throws InvalidInterfaceException
1035
-     * @throws ReflectionException
1036
-     */
1037
-    public function tax_total()
1038
-    {
1039
-        $tax_line_item = $this->tax_total_line_item();
1040
-        if ($tax_line_item) {
1041
-            return (float) $tax_line_item->total();
1042
-        }
1043
-        return (float) 0;
1044
-    }
1045
-
1046
-
1047
-    /**
1048
-     * Gets the tax subtotal line item (assumes there's only one)
1049
-     *
1050
-     * @return EE_Line_Item
1051
-     * @throws EE_Error
1052
-     * @throws InvalidArgumentException
1053
-     * @throws InvalidDataTypeException
1054
-     * @throws InvalidInterfaceException
1055
-     * @throws ReflectionException
1056
-     */
1057
-    public function tax_total_line_item()
1058
-    {
1059
-        return EEH_Line_Item::get_taxes_subtotal($this->total_line_item());
1060
-    }
1061
-
1062
-
1063
-    /**
1064
-     * Gets the array of billing info for the gateway and for this transaction's primary registration's attendee.
1065
-     *
1066
-     * @return EE_Form_Section_Proper
1067
-     * @throws EE_Error
1068
-     * @throws InvalidArgumentException
1069
-     * @throws InvalidDataTypeException
1070
-     * @throws InvalidInterfaceException
1071
-     * @throws ReflectionException
1072
-     */
1073
-    public function billing_info()
1074
-    {
1075
-        $payment_method = $this->payment_method();
1076
-        if (! $payment_method) {
1077
-            EE_Error::add_error(
1078
-                __(
1079
-                    'Could not find billing info for transaction because no gateway has been used for it yet',
1080
-                    'event_espresso'
1081
-                ),
1082
-                __FILE__,
1083
-                __FUNCTION__,
1084
-                __LINE__
1085
-            );
1086
-            return null;
1087
-        }
1088
-        $primary_reg = $this->primary_registration();
1089
-        if (! $primary_reg) {
1090
-            EE_Error::add_error(
1091
-                __(
1092
-                    'Cannot get billing info for gateway %s on transaction because no primary registration exists',
1093
-                    'event_espresso'
1094
-                ),
1095
-                __FILE__,
1096
-                __FUNCTION__,
1097
-                __LINE__
1098
-            );
1099
-            return null;
1100
-        }
1101
-        $attendee = $primary_reg->attendee();
1102
-        if (! $attendee) {
1103
-            EE_Error::add_error(
1104
-                __(
1105
-                    'Cannot get billing info for gateway %s on transaction because the primary registration has no attendee exists',
1106
-                    'event_espresso'
1107
-                ),
1108
-                __FILE__,
1109
-                __FUNCTION__,
1110
-                __LINE__
1111
-            );
1112
-            return null;
1113
-        }
1114
-        return $attendee->billing_info_for_payment_method($payment_method);
1115
-    }
1116
-
1117
-
1118
-    /**
1119
-     * Gets PMD_ID
1120
-     *
1121
-     * @return int
1122
-     * @throws EE_Error
1123
-     * @throws InvalidArgumentException
1124
-     * @throws InvalidDataTypeException
1125
-     * @throws InvalidInterfaceException
1126
-     * @throws ReflectionException
1127
-     */
1128
-    public function payment_method_ID()
1129
-    {
1130
-        return $this->get('PMD_ID');
1131
-    }
1132
-
1133
-
1134
-    /**
1135
-     * Sets PMD_ID
1136
-     *
1137
-     * @param int $PMD_ID
1138
-     * @throws EE_Error
1139
-     * @throws InvalidArgumentException
1140
-     * @throws InvalidDataTypeException
1141
-     * @throws InvalidInterfaceException
1142
-     * @throws ReflectionException
1143
-     */
1144
-    public function set_payment_method_ID($PMD_ID)
1145
-    {
1146
-        $this->set('PMD_ID', $PMD_ID);
1147
-    }
1148
-
1149
-
1150
-    /**
1151
-     * Gets the last-used payment method on this transaction
1152
-     * (we COULD just use the last-made payment, but some payment methods, namely
1153
-     * offline ones, dont' create payments)
1154
-     *
1155
-     * @return EE_Payment_Method
1156
-     * @throws EE_Error
1157
-     * @throws InvalidArgumentException
1158
-     * @throws InvalidDataTypeException
1159
-     * @throws InvalidInterfaceException
1160
-     * @throws ReflectionException
1161
-     */
1162
-    public function payment_method()
1163
-    {
1164
-        $pm = $this->get_first_related('Payment_Method');
1165
-        if ($pm instanceof EE_Payment_Method) {
1166
-            return $pm;
1167
-        }
1168
-        $last_payment = $this->last_payment();
1169
-        if ($last_payment instanceof EE_Payment && $last_payment->payment_method()) {
1170
-            return $last_payment->payment_method();
1171
-        }
1172
-        return null;
1173
-    }
1174
-
1175
-
1176
-    /**
1177
-     * Gets the last payment made
1178
-     *
1179
-     * @return EE_Base_Class|EE_Payment
1180
-     * @throws EE_Error
1181
-     * @throws InvalidArgumentException
1182
-     * @throws InvalidDataTypeException
1183
-     * @throws InvalidInterfaceException
1184
-     * @throws ReflectionException
1185
-     */
1186
-    public function last_payment()
1187
-    {
1188
-        return $this->get_first_related('Payment', array('order_by' => array('PAY_ID' => 'desc')));
1189
-    }
1190
-
1191
-
1192
-    /**
1193
-     * Gets all the line items which are unrelated to tickets on this transaction
1194
-     *
1195
-     * @return EE_Line_Item[]
1196
-     * @throws EE_Error
1197
-     * @throws InvalidArgumentException
1198
-     * @throws InvalidDataTypeException
1199
-     * @throws InvalidInterfaceException
1200
-     * @throws ReflectionException
1201
-     */
1202
-    public function non_ticket_line_items()
1203
-    {
1204
-        return EEM_Line_Item::instance()->get_all_non_ticket_line_items_for_transaction($this->ID());
1205
-    }
1206
-
1207
-
1208
-    /**
1209
-     * possibly toggles TXN status
1210
-     *
1211
-     * @param  boolean $update whether to save the TXN
1212
-     * @return bool whether the TXN was saved
1213
-     * @throws EE_Error
1214
-     * @throws InvalidArgumentException
1215
-     * @throws InvalidDataTypeException
1216
-     * @throws InvalidInterfaceException
1217
-     * @throws ReflectionException
1218
-     * @throws RuntimeException
1219
-     */
1220
-    public function update_status_based_on_total_paid($update = true)
1221
-    {
1222
-        // set transaction status based on comparison of TXN_paid vs TXN_total
1223
-        if (EEH_Money::compare_floats($this->paid(), $this->total(), '>')) {
1224
-            $new_txn_status = EEM_Transaction::overpaid_status_code;
1225
-        } elseif (EEH_Money::compare_floats($this->paid(), $this->total())) {
1226
-            $new_txn_status = EEM_Transaction::complete_status_code;
1227
-        } elseif (EEH_Money::compare_floats($this->paid(), $this->total(), '<')) {
1228
-            $new_txn_status = EEM_Transaction::incomplete_status_code;
1229
-        } else {
1230
-            throw new RuntimeException(
1231
-                __('The total paid calculation for this transaction is inaccurate.', 'event_espresso')
1232
-            );
1233
-        }
1234
-        if ($new_txn_status !== $this->status_ID()) {
1235
-            $this->set_status($new_txn_status);
1236
-            if ($update) {
1237
-                return $this->save() ? true : false;
1238
-            }
1239
-        }
1240
-        return false;
1241
-    }
1242
-
1243
-
1244
-    /**
1245
-     * Updates the transaction's status and total_paid based on all the payments
1246
-     * that apply to it
1247
-     *
1248
-     * @deprecated
1249
-     * @return array|bool
1250
-     * @throws EE_Error
1251
-     * @throws InvalidArgumentException
1252
-     * @throws ReflectionException
1253
-     * @throws InvalidDataTypeException
1254
-     * @throws InvalidInterfaceException
1255
-     */
1256
-    public function update_based_on_payments()
1257
-    {
1258
-        EE_Error::doing_it_wrong(
1259
-            __CLASS__ . '::' . __FUNCTION__,
1260
-            sprintf(
1261
-                __('This method is deprecated. Please use "%s" instead', 'event_espresso'),
1262
-                'EE_Transaction_Processor::update_transaction_and_registrations_after_checkout_or_payment()'
1263
-            ),
1264
-            '4.6.0'
1265
-        );
1266
-        /** @type EE_Transaction_Processor $transaction_processor */
1267
-        $transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
1268
-        return $transaction_processor->update_transaction_and_registrations_after_checkout_or_payment($this);
1269
-    }
1270
-
1271
-
1272
-    /**
1273
-     * @return string
1274
-     */
1275
-    public function old_txn_status()
1276
-    {
1277
-        return $this->_old_txn_status;
1278
-    }
1279
-
1280
-
1281
-    /**
1282
-     * @param string $old_txn_status
1283
-     */
1284
-    public function set_old_txn_status($old_txn_status)
1285
-    {
1286
-        // only set the first time
1287
-        if ($this->_old_txn_status === null) {
1288
-            $this->_old_txn_status = $old_txn_status;
1289
-        }
1290
-    }
1291
-
1292
-
1293
-    /**
1294
-     * reg_status_updated
1295
-     *
1296
-     * @return bool
1297
-     * @throws EE_Error
1298
-     * @throws InvalidArgumentException
1299
-     * @throws InvalidDataTypeException
1300
-     * @throws InvalidInterfaceException
1301
-     * @throws ReflectionException
1302
-     */
1303
-    public function txn_status_updated()
1304
-    {
1305
-        return $this->status_ID() !== $this->_old_txn_status && $this->_old_txn_status !== null;
1306
-    }
1307
-
1308
-
1309
-    /**
1310
-     * _reg_steps_completed
1311
-     * if $check_all is TRUE, then returns TRUE if ALL reg steps have been marked as completed,
1312
-     * if a $reg_step_slug is provided, then this step will be skipped when testing for completion
1313
-     * if $check_all is FALSE and a $reg_step_slug is provided, then ONLY that reg step will be tested for completion
1314
-     *
1315
-     * @param string $reg_step_slug
1316
-     * @param bool   $check_all
1317
-     * @return bool|int
1318
-     * @throws EE_Error
1319
-     * @throws InvalidArgumentException
1320
-     * @throws InvalidDataTypeException
1321
-     * @throws InvalidInterfaceException
1322
-     * @throws ReflectionException
1323
-     */
1324
-    private function _reg_steps_completed($reg_step_slug = '', $check_all = true)
1325
-    {
1326
-        $reg_steps = $this->reg_steps();
1327
-        if (! is_array($reg_steps) || empty($reg_steps)) {
1328
-            return false;
1329
-        }
1330
-        // loop thru reg steps array)
1331
-        foreach ($reg_steps as $slug => $reg_step_completed) {
1332
-            // if NOT checking ALL steps (only checking one step)
1333
-            if (! $check_all) {
1334
-                // and this is the one
1335
-                if ($slug === $reg_step_slug) {
1336
-                    return $reg_step_completed;
1337
-                }
1338
-                // skip to next reg step in loop
1339
-                continue;
1340
-            }
1341
-            // $check_all must be true, else we would never have gotten to this point
1342
-            if ($slug === $reg_step_slug) {
1343
-                // if we reach this point, then we are testing either:
1344
-                // all_reg_steps_completed_except() or
1345
-                // all_reg_steps_completed_except_final_step(),
1346
-                // and since this is the reg step EXCEPTION being tested
1347
-                // we want to return true (yes true) if this reg step is NOT completed
1348
-                // ie: "is everything completed except the final step?"
1349
-                // "that is correct... the final step is not completed, but all others are."
1350
-                return $reg_step_completed !== true;
1351
-            }
1352
-            if ($reg_step_completed !== true) {
1353
-                // if any reg step is NOT completed, then ALL steps are not completed
1354
-                return false;
1355
-            }
1356
-        }
1357
-        return true;
1358
-    }
1359
-
1360
-
1361
-    /**
1362
-     * all_reg_steps_completed
1363
-     * returns:
1364
-     *    true if ALL reg steps have been marked as completed
1365
-     *        or false if any step is not completed
1366
-     *
1367
-     * @return bool
1368
-     * @throws EE_Error
1369
-     * @throws InvalidArgumentException
1370
-     * @throws InvalidDataTypeException
1371
-     * @throws InvalidInterfaceException
1372
-     * @throws ReflectionException
1373
-     */
1374
-    public function all_reg_steps_completed()
1375
-    {
1376
-        return $this->_reg_steps_completed();
1377
-    }
1378
-
1379
-
1380
-    /**
1381
-     * all_reg_steps_completed_except
1382
-     * returns:
1383
-     *        true if ALL reg steps, except a particular step that you wish to skip over, have been marked as completed
1384
-     *        or false if any other step is not completed
1385
-     *        or false if ALL steps are completed including the exception you are testing !!!
1386
-     *
1387
-     * @param string $exception
1388
-     * @return bool
1389
-     * @throws EE_Error
1390
-     * @throws InvalidArgumentException
1391
-     * @throws InvalidDataTypeException
1392
-     * @throws InvalidInterfaceException
1393
-     * @throws ReflectionException
1394
-     */
1395
-    public function all_reg_steps_completed_except($exception = '')
1396
-    {
1397
-        return $this->_reg_steps_completed($exception);
1398
-    }
1399
-
1400
-
1401
-    /**
1402
-     * all_reg_steps_completed_except
1403
-     * returns:
1404
-     *        true if ALL reg steps, except the final step, have been marked as completed
1405
-     *        or false if any step is not completed
1406
-     *    or false if ALL steps are completed including the final step !!!
1407
-     *
1408
-     * @return bool
1409
-     * @throws EE_Error
1410
-     * @throws InvalidArgumentException
1411
-     * @throws InvalidDataTypeException
1412
-     * @throws InvalidInterfaceException
1413
-     * @throws ReflectionException
1414
-     */
1415
-    public function all_reg_steps_completed_except_final_step()
1416
-    {
1417
-        return $this->_reg_steps_completed('finalize_registration');
1418
-    }
1419
-
1420
-
1421
-    /**
1422
-     * reg_step_completed
1423
-     * returns:
1424
-     *    true if a specific reg step has been marked as completed
1425
-     *    a Unix timestamp if it has been initialized but not yet completed,
1426
-     *    or false if it has not yet been initialized
1427
-     *
1428
-     * @param string $reg_step_slug
1429
-     * @return bool|int
1430
-     * @throws EE_Error
1431
-     * @throws InvalidArgumentException
1432
-     * @throws InvalidDataTypeException
1433
-     * @throws InvalidInterfaceException
1434
-     * @throws ReflectionException
1435
-     */
1436
-    public function reg_step_completed($reg_step_slug)
1437
-    {
1438
-        return $this->_reg_steps_completed($reg_step_slug, false);
1439
-    }
1440
-
1441
-
1442
-    /**
1443
-     * completed_final_reg_step
1444
-     * returns:
1445
-     *    true if the finalize_registration reg step has been marked as completed
1446
-     *    a Unix timestamp if it has been initialized but not yet completed,
1447
-     *    or false if it has not yet been initialized
1448
-     *
1449
-     * @return bool|int
1450
-     * @throws EE_Error
1451
-     * @throws InvalidArgumentException
1452
-     * @throws InvalidDataTypeException
1453
-     * @throws InvalidInterfaceException
1454
-     * @throws ReflectionException
1455
-     */
1456
-    public function final_reg_step_completed()
1457
-    {
1458
-        return $this->_reg_steps_completed('finalize_registration', false);
1459
-    }
1460
-
1461
-
1462
-    /**
1463
-     * set_reg_step_initiated
1464
-     * given a valid TXN_reg_step, this sets it's value to a unix timestamp
1465
-     *
1466
-     * @param string $reg_step_slug
1467
-     * @return boolean
1468
-     * @throws EE_Error
1469
-     * @throws InvalidArgumentException
1470
-     * @throws InvalidDataTypeException
1471
-     * @throws InvalidInterfaceException
1472
-     * @throws ReflectionException
1473
-     */
1474
-    public function set_reg_step_initiated($reg_step_slug)
1475
-    {
1476
-        return $this->_set_reg_step_completed_status($reg_step_slug, time());
1477
-    }
1478
-
1479
-
1480
-    /**
1481
-     * set_reg_step_completed
1482
-     * given a valid TXN_reg_step, this sets the step as completed
1483
-     *
1484
-     * @param string $reg_step_slug
1485
-     * @return boolean
1486
-     * @throws EE_Error
1487
-     * @throws InvalidArgumentException
1488
-     * @throws InvalidDataTypeException
1489
-     * @throws InvalidInterfaceException
1490
-     * @throws ReflectionException
1491
-     */
1492
-    public function set_reg_step_completed($reg_step_slug)
1493
-    {
1494
-        return $this->_set_reg_step_completed_status($reg_step_slug, true);
1495
-    }
1496
-
1497
-
1498
-    /**
1499
-     * set_reg_step_completed
1500
-     * given a valid TXN_reg_step slug, this sets the step as NOT completed
1501
-     *
1502
-     * @param string $reg_step_slug
1503
-     * @return boolean
1504
-     * @throws EE_Error
1505
-     * @throws InvalidArgumentException
1506
-     * @throws InvalidDataTypeException
1507
-     * @throws InvalidInterfaceException
1508
-     * @throws ReflectionException
1509
-     */
1510
-    public function set_reg_step_not_completed($reg_step_slug)
1511
-    {
1512
-        return $this->_set_reg_step_completed_status($reg_step_slug, false);
1513
-    }
1514
-
1515
-
1516
-    /**
1517
-     * set_reg_step_completed
1518
-     * given a valid reg step slug, this sets the TXN_reg_step completed status which is either:
1519
-     *
1520
-     * @param  string      $reg_step_slug
1521
-     * @param  boolean|int $status
1522
-     * @return boolean
1523
-     * @throws EE_Error
1524
-     * @throws InvalidArgumentException
1525
-     * @throws InvalidDataTypeException
1526
-     * @throws InvalidInterfaceException
1527
-     * @throws ReflectionException
1528
-     */
1529
-    private function _set_reg_step_completed_status($reg_step_slug, $status)
1530
-    {
1531
-        // validate status
1532
-        $status = is_bool($status) || is_int($status) ? $status : false;
1533
-        // get reg steps array
1534
-        $txn_reg_steps = $this->reg_steps();
1535
-        // if reg step does NOT exist
1536
-        if (! isset($txn_reg_steps[ $reg_step_slug ])) {
1537
-            return false;
1538
-        }
1539
-        // if  we're trying to complete a step that is already completed
1540
-        if ($txn_reg_steps[ $reg_step_slug ] === true) {
1541
-            return true;
1542
-        }
1543
-        // if  we're trying to complete a step that hasn't even started
1544
-        if ($status === true && $txn_reg_steps[ $reg_step_slug ] === false) {
1545
-            return false;
1546
-        }
1547
-        // if current status value matches the incoming value (no change)
1548
-        // type casting as int means values should collapse to either 0, 1, or a timestamp like 1234567890
1549
-        if ((int) $txn_reg_steps[ $reg_step_slug ] === (int) $status) {
1550
-            // this will happen in cases where multiple AJAX requests occur during the same step
1551
-            return true;
1552
-        }
1553
-        // if we're trying to set a start time, but it has already been set...
1554
-        if (is_numeric($status) && is_numeric($txn_reg_steps[ $reg_step_slug ])) {
1555
-            // skip the update below, but don't return FALSE so that errors won't be displayed
1556
-            return true;
1557
-        }
1558
-        // update completed status
1559
-        $txn_reg_steps[ $reg_step_slug ] = $status;
1560
-        $this->set_reg_steps($txn_reg_steps);
1561
-        $this->save();
1562
-        return true;
1563
-    }
1564
-
1565
-
1566
-    /**
1567
-     * remove_reg_step
1568
-     * given a valid TXN_reg_step slug, this will remove (unset)
1569
-     * the reg step from the TXN reg step array
1570
-     *
1571
-     * @param string $reg_step_slug
1572
-     * @return void
1573
-     * @throws EE_Error
1574
-     * @throws InvalidArgumentException
1575
-     * @throws InvalidDataTypeException
1576
-     * @throws InvalidInterfaceException
1577
-     * @throws ReflectionException
1578
-     */
1579
-    public function remove_reg_step($reg_step_slug)
1580
-    {
1581
-        // get reg steps array
1582
-        $txn_reg_steps = $this->reg_steps();
1583
-        unset($txn_reg_steps[ $reg_step_slug ]);
1584
-        $this->set_reg_steps($txn_reg_steps);
1585
-    }
1586
-
1587
-
1588
-    /**
1589
-     * toggle_failed_transaction_status
1590
-     * upgrades a TXNs status from failed to abandoned,
1591
-     * meaning that contact information has been captured for at least one registrant
1592
-     *
1593
-     * @param bool $save
1594
-     * @return bool
1595
-     * @throws EE_Error
1596
-     * @throws InvalidArgumentException
1597
-     * @throws InvalidDataTypeException
1598
-     * @throws InvalidInterfaceException
1599
-     * @throws ReflectionException
1600
-     */
1601
-    public function toggle_failed_transaction_status($save = true)
1602
-    {
1603
-        // if TXN status is still set as "failed"...
1604
-        if ($this->status_ID() === EEM_Transaction::failed_status_code) {
1605
-            $this->set_status(EEM_Transaction::abandoned_status_code);
1606
-            if ($save) {
1607
-                $this->save();
1608
-            }
1609
-            return true;
1610
-        }
1611
-        return false;
1612
-    }
1613
-
1614
-
1615
-    /**
1616
-     * toggle_abandoned_transaction_status
1617
-     * upgrades a TXNs status from failed or abandoned to incomplete
1618
-     *
1619
-     * @return bool
1620
-     * @throws EE_Error
1621
-     * @throws InvalidArgumentException
1622
-     * @throws InvalidDataTypeException
1623
-     * @throws InvalidInterfaceException
1624
-     * @throws ReflectionException
1625
-     */
1626
-    public function toggle_abandoned_transaction_status()
1627
-    {
1628
-        // if TXN status has not been updated already due to a payment, and is still set as "failed" or "abandoned"...
1629
-        $txn_status = $this->status_ID();
1630
-        if ($txn_status === EEM_Transaction::failed_status_code
1631
-            || $txn_status === EEM_Transaction::abandoned_status_code
1632
-        ) {
1633
-            // if a contact record for the primary registrant has been created
1634
-            if ($this->primary_registration() instanceof EE_Registration
1635
-                && $this->primary_registration()->attendee() instanceof EE_Attendee
1636
-            ) {
1637
-                $this->set_status(EEM_Transaction::incomplete_status_code);
1638
-            } else {
1639
-                // no contact record? yer abandoned!
1640
-                $this->set_status(EEM_Transaction::abandoned_status_code);
1641
-            }
1642
-            return true;
1643
-        }
1644
-        return false;
1645
-    }
1646
-
1647
-
1648
-    /**
1649
-     * checks if an Abandoned TXN has any related payments, and if so,
1650
-     * updates the TXN status based on the amount paid
1651
-     *
1652
-     * @throws EE_Error
1653
-     * @throws InvalidDataTypeException
1654
-     * @throws InvalidInterfaceException
1655
-     * @throws InvalidArgumentException
1656
-     * @throws RuntimeException
1657
-     * @throws ReflectionException
1658
-     */
1659
-    public function verify_abandoned_transaction_status()
1660
-    {
1661
-        if ($this->status_ID() !== EEM_Transaction::abandoned_status_code) {
1662
-            return;
1663
-        }
1664
-        $payments = $this->get_many_related('Payment');
1665
-        if (! empty($payments)) {
1666
-            foreach ($payments as $payment) {
1667
-                if ($payment instanceof EE_Payment) {
1668
-                    // kk this TXN should NOT be abandoned
1669
-                    $this->update_status_based_on_total_paid();
1670
-                    if (! (defined('DOING_AJAX') && DOING_AJAX) && is_admin()) {
1671
-                        EE_Error::add_attention(
1672
-                            sprintf(
1673
-                                esc_html__(
1674
-                                    'The status for Transaction #%1$d has been updated from "Abandoned" to "%2$s", because at least one payment has been made towards it. If the payment appears in the "Payment Details" table below, you may need to edit its status and/or other details as well.',
1675
-                                    'event_espresso'
1676
-                                ),
1677
-                                $this->ID(),
1678
-                                $this->pretty_status()
1679
-                            )
1680
-                        );
1681
-                    }
1682
-                    // get final reg step status
1683
-                    $finalized = $this->final_reg_step_completed();
1684
-                    // if the 'finalize_registration' step has been initiated (has a timestamp)
1685
-                    // but has not yet been fully completed (TRUE)
1686
-                    if (is_int($finalized) && $finalized !== false && $finalized !== true) {
1687
-                        $this->set_reg_step_completed('finalize_registration');
1688
-                        $this->save();
1689
-                    }
1690
-                }
1691
-            }
1692
-        }
1693
-    }
16
+	/**
17
+	 * The length of time in seconds that a lock is applied before being considered expired.
18
+	 * It is not long because a transaction should only be locked for the duration of the request that locked it
19
+	 */
20
+	const LOCK_EXPIRATION = 2;
21
+
22
+	/**
23
+	 * txn status upon initial construction.
24
+	 *
25
+	 * @var string
26
+	 */
27
+	protected $_old_txn_status;
28
+
29
+
30
+	/**
31
+	 * @param array  $props_n_values          incoming values
32
+	 * @param string $timezone                incoming timezone
33
+	 *                                        (if not set the timezone set for the website will be used.)
34
+	 * @param array  $date_formats            incoming date_formats in an array where the first value is the
35
+	 *                                        date_format and the second value is the time format
36
+	 * @return EE_Transaction
37
+	 * @throws EE_Error
38
+	 * @throws InvalidArgumentException
39
+	 * @throws InvalidDataTypeException
40
+	 * @throws InvalidInterfaceException
41
+	 * @throws ReflectionException
42
+	 */
43
+	public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
44
+	{
45
+		$has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
46
+		$txn = $has_object
47
+			? $has_object
48
+			: new self($props_n_values, false, $timezone, $date_formats);
49
+		if (! $has_object) {
50
+			$txn->set_old_txn_status($txn->status_ID());
51
+		}
52
+		return $txn;
53
+	}
54
+
55
+
56
+	/**
57
+	 * @param array  $props_n_values  incoming values from the database
58
+	 * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
59
+	 *                                the website will be used.
60
+	 * @return EE_Transaction
61
+	 * @throws EE_Error
62
+	 * @throws InvalidArgumentException
63
+	 * @throws InvalidDataTypeException
64
+	 * @throws InvalidInterfaceException
65
+	 * @throws ReflectionException
66
+	 */
67
+	public static function new_instance_from_db($props_n_values = array(), $timezone = null)
68
+	{
69
+		$txn = new self($props_n_values, true, $timezone);
70
+		$txn->set_old_txn_status($txn->status_ID());
71
+		return $txn;
72
+	}
73
+
74
+
75
+	/**
76
+	 * Sets a meta field indicating that this TXN is locked and should not be updated in the db.
77
+	 * If a lock has already been set, then we will attempt to remove it in case it has expired.
78
+	 * If that also fails, then an exception is thrown.
79
+	 *
80
+	 * @throws EE_Error
81
+	 * @throws InvalidArgumentException
82
+	 * @throws InvalidDataTypeException
83
+	 * @throws InvalidInterfaceException
84
+	 * @throws ReflectionException
85
+	 */
86
+	public function lock()
87
+	{
88
+		// attempt to set lock, but if that fails...
89
+		if (! $this->add_extra_meta('lock', time(), true)) {
90
+			// then attempt to remove the lock in case it is expired
91
+			if ($this->_remove_expired_lock()) {
92
+				// if removal was successful, then try setting lock again
93
+				$this->lock();
94
+			} else {
95
+				// but if the lock can not be removed, then throw an exception
96
+				throw new EE_Error(
97
+					sprintf(
98
+						__(
99
+							'Could not lock Transaction %1$d because it is already locked, meaning another part of the system is currently editing it. It should already be unlocked by the time you read this, so please refresh the page and try again.',
100
+							'event_espresso'
101
+						),
102
+						$this->ID()
103
+					)
104
+				);
105
+			}
106
+		}
107
+	}
108
+
109
+
110
+	/**
111
+	 * removes transaction lock applied in EE_Transaction::lock()
112
+	 *
113
+	 * @return int
114
+	 * @throws EE_Error
115
+	 * @throws InvalidArgumentException
116
+	 * @throws InvalidDataTypeException
117
+	 * @throws InvalidInterfaceException
118
+	 * @throws ReflectionException
119
+	 */
120
+	public function unlock()
121
+	{
122
+		return $this->delete_extra_meta('lock');
123
+	}
124
+
125
+
126
+	/**
127
+	 * Decides whether or not now is the right time to update the transaction.
128
+	 * This is useful because we don't always know if it is safe to update the transaction
129
+	 * and its related data. why?
130
+	 * because it's possible that the transaction is being used in another
131
+	 * request and could overwrite anything we save.
132
+	 * So we want to only update the txn once we know that won't happen.
133
+	 * We also check that the lock isn't expired, and remove it if it is
134
+	 *
135
+	 * @return boolean
136
+	 * @throws EE_Error
137
+	 * @throws InvalidArgumentException
138
+	 * @throws InvalidDataTypeException
139
+	 * @throws InvalidInterfaceException
140
+	 * @throws ReflectionException
141
+	 */
142
+	public function is_locked()
143
+	{
144
+		// if TXN is not locked, then return false immediately
145
+		if (! $this->_get_lock()) {
146
+			return false;
147
+		}
148
+		// if not, then let's try and remove the lock in case it's expired...
149
+		// _remove_expired_lock() returns 0 when lock is valid (ie: removed = false)
150
+		// and a positive number if the lock was removed (ie: number of locks deleted),
151
+		// so we need to return the opposite
152
+		return ! $this->_remove_expired_lock() ? true : false;
153
+	}
154
+
155
+
156
+	/**
157
+	 * Gets the meta field indicating that this TXN is locked
158
+	 *
159
+	 * @return int
160
+	 * @throws EE_Error
161
+	 * @throws InvalidArgumentException
162
+	 * @throws InvalidDataTypeException
163
+	 * @throws InvalidInterfaceException
164
+	 * @throws ReflectionException
165
+	 */
166
+	protected function _get_lock()
167
+	{
168
+		return (int) $this->get_extra_meta('lock', true, 0);
169
+	}
170
+
171
+
172
+	/**
173
+	 * If the lock on this transaction is expired, then we want to remove it so that the transaction can be updated
174
+	 *
175
+	 * @return int
176
+	 * @throws EE_Error
177
+	 * @throws InvalidArgumentException
178
+	 * @throws InvalidDataTypeException
179
+	 * @throws InvalidInterfaceException
180
+	 * @throws ReflectionException
181
+	 */
182
+	protected function _remove_expired_lock()
183
+	{
184
+		$locked = $this->_get_lock();
185
+		if ($locked && time() - EE_Transaction::LOCK_EXPIRATION > $locked) {
186
+			return $this->unlock();
187
+		}
188
+		return 0;
189
+	}
190
+
191
+
192
+	/**
193
+	 * Set transaction total
194
+	 *
195
+	 * @param float $total total value of transaction
196
+	 * @throws EE_Error
197
+	 * @throws InvalidArgumentException
198
+	 * @throws InvalidDataTypeException
199
+	 * @throws InvalidInterfaceException
200
+	 * @throws ReflectionException
201
+	 */
202
+	public function set_total($total = 0.00)
203
+	{
204
+		$this->set('TXN_total', (float) $total);
205
+	}
206
+
207
+
208
+	/**
209
+	 * Set Total Amount Paid to Date
210
+	 *
211
+	 * @param float $total_paid total amount paid to date (sum of all payments)
212
+	 * @throws EE_Error
213
+	 * @throws InvalidArgumentException
214
+	 * @throws InvalidDataTypeException
215
+	 * @throws InvalidInterfaceException
216
+	 * @throws ReflectionException
217
+	 */
218
+	public function set_paid($total_paid = 0.00)
219
+	{
220
+		$this->set('TXN_paid', (float) $total_paid);
221
+	}
222
+
223
+
224
+	/**
225
+	 * Set transaction status
226
+	 *
227
+	 * @param string $status        whether the transaction is open, declined, accepted,
228
+	 *                              or any number of custom values that can be set
229
+	 * @throws EE_Error
230
+	 * @throws InvalidArgumentException
231
+	 * @throws InvalidDataTypeException
232
+	 * @throws InvalidInterfaceException
233
+	 * @throws ReflectionException
234
+	 */
235
+	public function set_status($status = '')
236
+	{
237
+		$this->set('STS_ID', $status);
238
+	}
239
+
240
+
241
+	/**
242
+	 * Set hash salt
243
+	 *
244
+	 * @param string $hash_salt required for some payment gateways
245
+	 * @throws EE_Error
246
+	 * @throws InvalidArgumentException
247
+	 * @throws InvalidDataTypeException
248
+	 * @throws InvalidInterfaceException
249
+	 * @throws ReflectionException
250
+	 */
251
+	public function set_hash_salt($hash_salt = '')
252
+	{
253
+		$this->set('TXN_hash_salt', $hash_salt);
254
+	}
255
+
256
+
257
+	/**
258
+	 * Sets TXN_reg_steps array
259
+	 *
260
+	 * @param array $txn_reg_steps
261
+	 * @throws EE_Error
262
+	 * @throws InvalidArgumentException
263
+	 * @throws InvalidDataTypeException
264
+	 * @throws InvalidInterfaceException
265
+	 * @throws ReflectionException
266
+	 */
267
+	public function set_reg_steps(array $txn_reg_steps)
268
+	{
269
+		$this->set('TXN_reg_steps', $txn_reg_steps);
270
+	}
271
+
272
+
273
+	/**
274
+	 * Gets TXN_reg_steps
275
+	 *
276
+	 * @return array
277
+	 * @throws EE_Error
278
+	 * @throws InvalidArgumentException
279
+	 * @throws InvalidDataTypeException
280
+	 * @throws InvalidInterfaceException
281
+	 * @throws ReflectionException
282
+	 */
283
+	public function reg_steps()
284
+	{
285
+		$TXN_reg_steps = $this->get('TXN_reg_steps');
286
+		return is_array($TXN_reg_steps) ? (array) $TXN_reg_steps : array();
287
+	}
288
+
289
+
290
+	/**
291
+	 * @return string of transaction's total cost, with currency symbol and decimal
292
+	 * @throws EE_Error
293
+	 * @throws InvalidArgumentException
294
+	 * @throws InvalidDataTypeException
295
+	 * @throws InvalidInterfaceException
296
+	 * @throws ReflectionException
297
+	 */
298
+	public function pretty_total()
299
+	{
300
+		return $this->get_pretty('TXN_total');
301
+	}
302
+
303
+
304
+	/**
305
+	 * Gets the amount paid in a pretty string (formatted and with currency symbol)
306
+	 *
307
+	 * @return string
308
+	 * @throws EE_Error
309
+	 * @throws InvalidArgumentException
310
+	 * @throws InvalidDataTypeException
311
+	 * @throws InvalidInterfaceException
312
+	 * @throws ReflectionException
313
+	 */
314
+	public function pretty_paid()
315
+	{
316
+		return $this->get_pretty('TXN_paid');
317
+	}
318
+
319
+
320
+	/**
321
+	 * calculate the amount remaining for this transaction and return;
322
+	 *
323
+	 * @return float amount remaining
324
+	 * @throws EE_Error
325
+	 * @throws InvalidArgumentException
326
+	 * @throws InvalidDataTypeException
327
+	 * @throws InvalidInterfaceException
328
+	 * @throws ReflectionException
329
+	 */
330
+	public function remaining()
331
+	{
332
+		return $this->total() - $this->paid();
333
+	}
334
+
335
+
336
+	/**
337
+	 * get Transaction Total
338
+	 *
339
+	 * @return float
340
+	 * @throws EE_Error
341
+	 * @throws InvalidArgumentException
342
+	 * @throws InvalidDataTypeException
343
+	 * @throws InvalidInterfaceException
344
+	 * @throws ReflectionException
345
+	 */
346
+	public function total()
347
+	{
348
+		return (float) $this->get('TXN_total');
349
+	}
350
+
351
+
352
+	/**
353
+	 * get Total Amount Paid to Date
354
+	 *
355
+	 * @return float
356
+	 * @throws EE_Error
357
+	 * @throws InvalidArgumentException
358
+	 * @throws InvalidDataTypeException
359
+	 * @throws InvalidInterfaceException
360
+	 * @throws ReflectionException
361
+	 */
362
+	public function paid()
363
+	{
364
+		return (float) $this->get('TXN_paid');
365
+	}
366
+
367
+
368
+	/**
369
+	 * @return mixed|null
370
+	 * @throws EE_Error
371
+	 * @throws InvalidArgumentException
372
+	 * @throws InvalidDataTypeException
373
+	 * @throws InvalidInterfaceException
374
+	 * @throws ReflectionException
375
+	 */
376
+	public function get_cart_session()
377
+	{
378
+		$session_data = (array) $this->get('TXN_session_data');
379
+		return isset($session_data['cart']) && $session_data['cart'] instanceof EE_Cart
380
+			? $session_data['cart']
381
+			: null;
382
+	}
383
+
384
+
385
+	/**
386
+	 * get Transaction session data
387
+	 *
388
+	 * @return array|mixed
389
+	 * @throws EE_Error
390
+	 * @throws InvalidArgumentException
391
+	 * @throws InvalidDataTypeException
392
+	 * @throws InvalidInterfaceException
393
+	 * @throws ReflectionException
394
+	 */
395
+	public function session_data()
396
+	{
397
+		$session_data = $this->get('TXN_session_data');
398
+		if (empty($session_data)) {
399
+			$session_data = array(
400
+				'id'            => null,
401
+				'user_id'       => null,
402
+				'ip_address'    => null,
403
+				'user_agent'    => null,
404
+				'init_access'   => null,
405
+				'last_access'   => null,
406
+				'pages_visited' => array(),
407
+			);
408
+		}
409
+		return $session_data;
410
+	}
411
+
412
+
413
+	/**
414
+	 * Set session data within the TXN object
415
+	 *
416
+	 * @param EE_Session|array $session_data
417
+	 * @throws EE_Error
418
+	 * @throws InvalidArgumentException
419
+	 * @throws InvalidDataTypeException
420
+	 * @throws InvalidInterfaceException
421
+	 * @throws ReflectionException
422
+	 */
423
+	public function set_txn_session_data($session_data)
424
+	{
425
+		if ($session_data instanceof EE_Session) {
426
+			$this->set('TXN_session_data', $session_data->get_session_data(null, true));
427
+		} else {
428
+			$this->set('TXN_session_data', $session_data);
429
+		}
430
+	}
431
+
432
+
433
+	/**
434
+	 * get Transaction hash salt
435
+	 *
436
+	 * @return mixed
437
+	 * @throws EE_Error
438
+	 * @throws InvalidArgumentException
439
+	 * @throws InvalidDataTypeException
440
+	 * @throws InvalidInterfaceException
441
+	 * @throws ReflectionException
442
+	 */
443
+	public function hash_salt_()
444
+	{
445
+		return $this->get('TXN_hash_salt');
446
+	}
447
+
448
+
449
+	/**
450
+	 * Returns the transaction datetime as either:
451
+	 *            - unix timestamp format ($format = false, $gmt = true)
452
+	 *            - formatted date string including the UTC (timezone) offset ($format = true ($gmt
453
+	 *              has no affect with this option)), this also may include a timezone abbreviation if the
454
+	 *              set timezone in this class differs from what the timezone is on the blog.
455
+	 *            - formatted date string including the UTC (timezone) offset (default).
456
+	 *
457
+	 * @param boolean $format   - whether to return a unix timestamp (default) or formatted date string
458
+	 * @param boolean $gmt      - whether to return a unix timestamp with UTC offset applied (default)
459
+	 *                          or no UTC offset applied
460
+	 * @return string | int
461
+	 * @throws EE_Error
462
+	 * @throws InvalidArgumentException
463
+	 * @throws InvalidDataTypeException
464
+	 * @throws InvalidInterfaceException
465
+	 * @throws ReflectionException
466
+	 */
467
+	public function datetime($format = false, $gmt = false)
468
+	{
469
+		if ($format) {
470
+			return $this->get_pretty('TXN_timestamp');
471
+		}
472
+		if ($gmt) {
473
+			return $this->get_raw('TXN_timestamp');
474
+		}
475
+		return $this->get('TXN_timestamp');
476
+	}
477
+
478
+
479
+	/**
480
+	 * Gets registrations on this transaction
481
+	 *
482
+	 * @param array   $query_params array of query parameters
483
+	 * @param boolean $get_cached   TRUE to retrieve cached registrations or FALSE to pull from the db
484
+	 * @return EE_Base_Class[]|EE_Registration[]
485
+	 * @throws EE_Error
486
+	 * @throws InvalidArgumentException
487
+	 * @throws InvalidDataTypeException
488
+	 * @throws InvalidInterfaceException
489
+	 * @throws ReflectionException
490
+	 */
491
+	public function registrations($query_params = array(), $get_cached = false)
492
+	{
493
+		$query_params = (empty($query_params) || ! is_array($query_params))
494
+			? array(
495
+				'order_by' => array(
496
+					'Event.EVT_name'     => 'ASC',
497
+					'Attendee.ATT_lname' => 'ASC',
498
+					'Attendee.ATT_fname' => 'ASC',
499
+				),
500
+			)
501
+			: $query_params;
502
+		$query_params = $get_cached ? array() : $query_params;
503
+		return $this->get_many_related('Registration', $query_params);
504
+	}
505
+
506
+
507
+	/**
508
+	 * Gets all the attendees for this transaction (handy for use with EE_Attendee's get_registrations_for_event
509
+	 * function for getting attendees and how many registrations they each have for an event)
510
+	 *
511
+	 * @return mixed EE_Attendee[] by default, int if $output is set to 'COUNT'
512
+	 * @throws EE_Error
513
+	 * @throws InvalidArgumentException
514
+	 * @throws InvalidDataTypeException
515
+	 * @throws InvalidInterfaceException
516
+	 * @throws ReflectionException
517
+	 */
518
+	public function attendees()
519
+	{
520
+		return $this->get_many_related('Attendee', array(array('Registration.Transaction.TXN_ID' => $this->ID())));
521
+	}
522
+
523
+
524
+	/**
525
+	 * Gets payments for this transaction. Unlike other such functions, order by 'DESC' by default
526
+	 *
527
+	 * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
528
+	 * @return EE_Base_Class[]|EE_Payment[]
529
+	 * @throws EE_Error
530
+	 * @throws InvalidArgumentException
531
+	 * @throws InvalidDataTypeException
532
+	 * @throws InvalidInterfaceException
533
+	 * @throws ReflectionException
534
+	 */
535
+	public function payments($query_params = array())
536
+	{
537
+		return $this->get_many_related('Payment', $query_params);
538
+	}
539
+
540
+
541
+	/**
542
+	 * gets only approved payments for this transaction
543
+	 *
544
+	 * @return EE_Base_Class[]|EE_Payment[]
545
+	 * @throws EE_Error
546
+	 * @throws InvalidArgumentException
547
+	 * @throws ReflectionException
548
+	 * @throws InvalidDataTypeException
549
+	 * @throws InvalidInterfaceException
550
+	 */
551
+	public function approved_payments()
552
+	{
553
+		EE_Registry::instance()->load_model('Payment');
554
+		return $this->get_many_related(
555
+			'Payment',
556
+			array(
557
+				array('STS_ID' => EEM_Payment::status_id_approved),
558
+				'order_by' => array('PAY_timestamp' => 'DESC'),
559
+			)
560
+		);
561
+	}
562
+
563
+
564
+	/**
565
+	 * Gets all payments which have not been approved
566
+	 *
567
+	 * @return EE_Base_Class[]|EEI_Payment[]
568
+	 * @throws EE_Error if a model is misconfigured somehow
569
+	 * @throws InvalidArgumentException
570
+	 * @throws InvalidDataTypeException
571
+	 * @throws InvalidInterfaceException
572
+	 * @throws ReflectionException
573
+	 */
574
+	public function pending_payments()
575
+	{
576
+		return $this->get_many_related(
577
+			'Payment',
578
+			array(
579
+				array(
580
+					'STS_ID' => EEM_Payment::status_id_pending,
581
+				),
582
+				'order_by' => array(
583
+					'PAY_timestamp' => 'DESC',
584
+				),
585
+			)
586
+		);
587
+	}
588
+
589
+
590
+	/**
591
+	 * echoes $this->pretty_status()
592
+	 *
593
+	 * @param bool $show_icons
594
+	 * @throws EE_Error
595
+	 * @throws InvalidArgumentException
596
+	 * @throws InvalidDataTypeException
597
+	 * @throws InvalidInterfaceException
598
+	 * @throws ReflectionException
599
+	 */
600
+	public function e_pretty_status($show_icons = false)
601
+	{
602
+		echo $this->pretty_status($show_icons);
603
+	}
604
+
605
+
606
+	/**
607
+	 * returns a pretty version of the status, good for displaying to users
608
+	 *
609
+	 * @param bool $show_icons
610
+	 * @return string
611
+	 * @throws EE_Error
612
+	 * @throws InvalidArgumentException
613
+	 * @throws InvalidDataTypeException
614
+	 * @throws InvalidInterfaceException
615
+	 * @throws ReflectionException
616
+	 */
617
+	public function pretty_status($show_icons = false)
618
+	{
619
+		$status = EEM_Status::instance()->localized_status(
620
+			array($this->status_ID() => __('unknown', 'event_espresso')),
621
+			false,
622
+			'sentence'
623
+		);
624
+		$icon = '';
625
+		switch ($this->status_ID()) {
626
+			case EEM_Transaction::complete_status_code:
627
+				$icon = $show_icons ? '<span class="dashicons dashicons-yes ee-icon-size-24 green-text"></span>' : '';
628
+				break;
629
+			case EEM_Transaction::incomplete_status_code:
630
+				$icon = $show_icons ? '<span class="dashicons dashicons-marker ee-icon-size-16 lt-blue-text"></span>'
631
+					: '';
632
+				break;
633
+			case EEM_Transaction::abandoned_status_code:
634
+				$icon = $show_icons ? '<span class="dashicons dashicons-marker ee-icon-size-16 red-text"></span>' : '';
635
+				break;
636
+			case EEM_Transaction::failed_status_code:
637
+				$icon = $show_icons ? '<span class="dashicons dashicons-no ee-icon-size-16 red-text"></span>' : '';
638
+				break;
639
+			case EEM_Transaction::overpaid_status_code:
640
+				$icon = $show_icons ? '<span class="dashicons dashicons-plus ee-icon-size-16 orange-text"></span>' : '';
641
+				break;
642
+		}
643
+		return $icon . $status[ $this->status_ID() ];
644
+	}
645
+
646
+
647
+	/**
648
+	 * get Transaction Status
649
+	 *
650
+	 * @return mixed
651
+	 * @throws EE_Error
652
+	 * @throws InvalidArgumentException
653
+	 * @throws InvalidDataTypeException
654
+	 * @throws InvalidInterfaceException
655
+	 * @throws ReflectionException
656
+	 */
657
+	public function status_ID()
658
+	{
659
+		return $this->get('STS_ID');
660
+	}
661
+
662
+
663
+	/**
664
+	 * Returns TRUE or FALSE for whether or not this transaction cost any money
665
+	 *
666
+	 * @return boolean
667
+	 * @throws EE_Error
668
+	 * @throws InvalidArgumentException
669
+	 * @throws InvalidDataTypeException
670
+	 * @throws InvalidInterfaceException
671
+	 * @throws ReflectionException
672
+	 */
673
+	public function is_free()
674
+	{
675
+		return EEH_Money::compare_floats($this->get('TXN_total'), 0, '==');
676
+	}
677
+
678
+
679
+	/**
680
+	 * Returns whether this transaction is complete
681
+	 * Useful in templates and other logic for deciding if we should ask for another payment...
682
+	 *
683
+	 * @return boolean
684
+	 * @throws EE_Error
685
+	 * @throws InvalidArgumentException
686
+	 * @throws InvalidDataTypeException
687
+	 * @throws InvalidInterfaceException
688
+	 * @throws ReflectionException
689
+	 */
690
+	public function is_completed()
691
+	{
692
+		return $this->status_ID() === EEM_Transaction::complete_status_code;
693
+	}
694
+
695
+
696
+	/**
697
+	 * Returns whether this transaction is incomplete
698
+	 * Useful in templates and other logic for deciding if we should ask for another payment...
699
+	 *
700
+	 * @return boolean
701
+	 * @throws EE_Error
702
+	 * @throws InvalidArgumentException
703
+	 * @throws InvalidDataTypeException
704
+	 * @throws InvalidInterfaceException
705
+	 * @throws ReflectionException
706
+	 */
707
+	public function is_incomplete()
708
+	{
709
+		return $this->status_ID() === EEM_Transaction::incomplete_status_code;
710
+	}
711
+
712
+
713
+	/**
714
+	 * Returns whether this transaction is overpaid
715
+	 * Useful in templates and other logic for deciding if monies need to be refunded
716
+	 *
717
+	 * @return boolean
718
+	 * @throws EE_Error
719
+	 * @throws InvalidArgumentException
720
+	 * @throws InvalidDataTypeException
721
+	 * @throws InvalidInterfaceException
722
+	 * @throws ReflectionException
723
+	 */
724
+	public function is_overpaid()
725
+	{
726
+		return $this->status_ID() === EEM_Transaction::overpaid_status_code;
727
+	}
728
+
729
+
730
+	/**
731
+	 * Returns whether this transaction was abandoned
732
+	 * meaning that the transaction/registration process was somehow interrupted and never completed
733
+	 * but that contact information exists for at least one registrant
734
+	 *
735
+	 * @return boolean
736
+	 * @throws EE_Error
737
+	 * @throws InvalidArgumentException
738
+	 * @throws InvalidDataTypeException
739
+	 * @throws InvalidInterfaceException
740
+	 * @throws ReflectionException
741
+	 */
742
+	public function is_abandoned()
743
+	{
744
+		return $this->status_ID() === EEM_Transaction::abandoned_status_code;
745
+	}
746
+
747
+
748
+	/**
749
+	 * Returns whether this transaction failed
750
+	 * meaning that the transaction/registration process was somehow interrupted and never completed
751
+	 * and that NO contact information exists for any registrants
752
+	 *
753
+	 * @return boolean
754
+	 * @throws EE_Error
755
+	 * @throws InvalidArgumentException
756
+	 * @throws InvalidDataTypeException
757
+	 * @throws InvalidInterfaceException
758
+	 * @throws ReflectionException
759
+	 */
760
+	public function failed()
761
+	{
762
+		return $this->status_ID() === EEM_Transaction::failed_status_code;
763
+	}
764
+
765
+
766
+	/**
767
+	 * This returns the url for the invoice of this transaction
768
+	 *
769
+	 * @param string $type 'html' or 'pdf' (default is pdf)
770
+	 * @return string
771
+	 * @throws EE_Error
772
+	 * @throws InvalidArgumentException
773
+	 * @throws InvalidDataTypeException
774
+	 * @throws InvalidInterfaceException
775
+	 * @throws ReflectionException
776
+	 */
777
+	public function invoice_url($type = 'html')
778
+	{
779
+		$REG = $this->primary_registration();
780
+		if (! $REG instanceof EE_Registration) {
781
+			return '';
782
+		}
783
+		return $REG->invoice_url($type);
784
+	}
785
+
786
+
787
+	/**
788
+	 * Gets the primary registration only
789
+	 *
790
+	 * @return EE_Base_Class|EE_Registration
791
+	 * @throws EE_Error
792
+	 * @throws InvalidArgumentException
793
+	 * @throws InvalidDataTypeException
794
+	 * @throws InvalidInterfaceException
795
+	 * @throws ReflectionException
796
+	 */
797
+	public function primary_registration()
798
+	{
799
+		$registrations = (array) $this->get_many_related(
800
+			'Registration',
801
+			array(array('REG_count' => EEM_Registration::PRIMARY_REGISTRANT_COUNT))
802
+		);
803
+		foreach ($registrations as $registration) {
804
+			// valid registration that is NOT cancelled or declined ?
805
+			if ($registration instanceof EE_Registration
806
+				&& ! in_array($registration->status_ID(), EEM_Registration::closed_reg_statuses(), true)
807
+			) {
808
+				return $registration;
809
+			}
810
+		}
811
+		// nothing valid found, so just return first thing from array of results
812
+		return reset($registrations);
813
+	}
814
+
815
+
816
+	/**
817
+	 * Gets the URL for viewing the receipt
818
+	 *
819
+	 * @param string $type 'pdf' or 'html' (default is 'html')
820
+	 * @return string
821
+	 * @throws EE_Error
822
+	 * @throws InvalidArgumentException
823
+	 * @throws InvalidDataTypeException
824
+	 * @throws InvalidInterfaceException
825
+	 * @throws ReflectionException
826
+	 */
827
+	public function receipt_url($type = 'html')
828
+	{
829
+		$REG = $this->primary_registration();
830
+		if (! $REG instanceof EE_Registration) {
831
+			return '';
832
+		}
833
+		return $REG->receipt_url($type);
834
+	}
835
+
836
+
837
+	/**
838
+	 * Gets the URL of the thank you page with this registration REG_url_link added as
839
+	 * a query parameter
840
+	 *
841
+	 * @return string
842
+	 * @throws EE_Error
843
+	 * @throws InvalidArgumentException
844
+	 * @throws InvalidDataTypeException
845
+	 * @throws InvalidInterfaceException
846
+	 * @throws ReflectionException
847
+	 */
848
+	public function payment_overview_url()
849
+	{
850
+		$primary_registration = $this->primary_registration();
851
+		return $primary_registration instanceof EE_Registration ? $primary_registration->payment_overview_url() : false;
852
+	}
853
+
854
+
855
+	/**
856
+	 * @return string
857
+	 * @throws EE_Error
858
+	 * @throws InvalidArgumentException
859
+	 * @throws InvalidDataTypeException
860
+	 * @throws InvalidInterfaceException
861
+	 * @throws ReflectionException
862
+	 */
863
+	public function gateway_response_on_transaction()
864
+	{
865
+		$payment = $this->get_first_related('Payment');
866
+		return $payment instanceof EE_Payment ? $payment->gateway_response() : '';
867
+	}
868
+
869
+
870
+	/**
871
+	 * Get the status object of this object
872
+	 *
873
+	 * @return EE_Base_Class|EE_Status
874
+	 * @throws EE_Error
875
+	 * @throws InvalidArgumentException
876
+	 * @throws InvalidDataTypeException
877
+	 * @throws InvalidInterfaceException
878
+	 * @throws ReflectionException
879
+	 */
880
+	public function status_obj()
881
+	{
882
+		return $this->get_first_related('Status');
883
+	}
884
+
885
+
886
+	/**
887
+	 * Gets all the extra meta info on this payment
888
+	 *
889
+	 * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
890
+	 * @return EE_Base_Class[]|EE_Extra_Meta
891
+	 * @throws EE_Error
892
+	 * @throws InvalidArgumentException
893
+	 * @throws InvalidDataTypeException
894
+	 * @throws InvalidInterfaceException
895
+	 * @throws ReflectionException
896
+	 */
897
+	public function extra_meta($query_params = array())
898
+	{
899
+		return $this->get_many_related('Extra_Meta', $query_params);
900
+	}
901
+
902
+
903
+	/**
904
+	 * Wrapper for _add_relation_to
905
+	 *
906
+	 * @param EE_Registration $registration
907
+	 * @return EE_Base_Class the relation was added to
908
+	 * @throws EE_Error
909
+	 * @throws InvalidArgumentException
910
+	 * @throws InvalidDataTypeException
911
+	 * @throws InvalidInterfaceException
912
+	 * @throws ReflectionException
913
+	 */
914
+	public function add_registration(EE_Registration $registration)
915
+	{
916
+		return $this->_add_relation_to($registration, 'Registration');
917
+	}
918
+
919
+
920
+	/**
921
+	 * Removes the given registration from being related (even before saving this transaction).
922
+	 * If an ID/index is provided and this transaction isn't saved yet, removes it from list of cached relations
923
+	 *
924
+	 * @param int $registration_or_id
925
+	 * @return EE_Base_Class that was removed from being related
926
+	 * @throws EE_Error
927
+	 * @throws InvalidArgumentException
928
+	 * @throws InvalidDataTypeException
929
+	 * @throws InvalidInterfaceException
930
+	 * @throws ReflectionException
931
+	 */
932
+	public function remove_registration_with_id($registration_or_id)
933
+	{
934
+		return $this->_remove_relation_to($registration_or_id, 'Registration');
935
+	}
936
+
937
+
938
+	/**
939
+	 * Gets all the line items which are for ACTUAL items
940
+	 *
941
+	 * @return EE_Line_Item[]
942
+	 * @throws EE_Error
943
+	 * @throws InvalidArgumentException
944
+	 * @throws InvalidDataTypeException
945
+	 * @throws InvalidInterfaceException
946
+	 * @throws ReflectionException
947
+	 */
948
+	public function items_purchased()
949
+	{
950
+		return $this->line_items(array(array('LIN_type' => EEM_Line_Item::type_line_item)));
951
+	}
952
+
953
+
954
+	/**
955
+	 * Wrapper for _add_relation_to
956
+	 *
957
+	 * @param EE_Line_Item $line_item
958
+	 * @return EE_Base_Class the relation was added to
959
+	 * @throws EE_Error
960
+	 * @throws InvalidArgumentException
961
+	 * @throws InvalidDataTypeException
962
+	 * @throws InvalidInterfaceException
963
+	 * @throws ReflectionException
964
+	 */
965
+	public function add_line_item(EE_Line_Item $line_item)
966
+	{
967
+		return $this->_add_relation_to($line_item, 'Line_Item');
968
+	}
969
+
970
+
971
+	/**
972
+	 * Gets ALL the line items related to this transaction (unstructured)
973
+	 *
974
+	 * @param array $query_params
975
+	 * @return EE_Base_Class[]|EE_Line_Item[]
976
+	 * @throws EE_Error
977
+	 * @throws InvalidArgumentException
978
+	 * @throws InvalidDataTypeException
979
+	 * @throws InvalidInterfaceException
980
+	 * @throws ReflectionException
981
+	 */
982
+	public function line_items($query_params = array())
983
+	{
984
+		return $this->get_many_related('Line_Item', $query_params);
985
+	}
986
+
987
+
988
+	/**
989
+	 * Gets all the line items which are taxes on the total
990
+	 *
991
+	 * @return EE_Line_Item[]
992
+	 * @throws EE_Error
993
+	 * @throws InvalidArgumentException
994
+	 * @throws InvalidDataTypeException
995
+	 * @throws InvalidInterfaceException
996
+	 * @throws ReflectionException
997
+	 */
998
+	public function tax_items()
999
+	{
1000
+		return $this->line_items(array(array('LIN_type' => EEM_Line_Item::type_tax)));
1001
+	}
1002
+
1003
+
1004
+	/**
1005
+	 * Gets the total line item (which is a parent of all other related line items,
1006
+	 * meaning it takes them all into account on its total)
1007
+	 *
1008
+	 * @param bool $create_if_not_found
1009
+	 * @return \EE_Line_Item
1010
+	 * @throws EE_Error
1011
+	 * @throws InvalidArgumentException
1012
+	 * @throws InvalidDataTypeException
1013
+	 * @throws InvalidInterfaceException
1014
+	 * @throws ReflectionException
1015
+	 */
1016
+	public function total_line_item($create_if_not_found = true)
1017
+	{
1018
+		$item = $this->get_first_related('Line_Item', array(array('LIN_type' => EEM_Line_Item::type_total)));
1019
+		if (! $item && $create_if_not_found) {
1020
+			$item = EEH_Line_Item::create_total_line_item($this);
1021
+		}
1022
+		return $item;
1023
+	}
1024
+
1025
+
1026
+	/**
1027
+	 * Returns the total amount of tax on this transaction
1028
+	 * (assumes there's only one tax subtotal line item)
1029
+	 *
1030
+	 * @return float
1031
+	 * @throws EE_Error
1032
+	 * @throws InvalidArgumentException
1033
+	 * @throws InvalidDataTypeException
1034
+	 * @throws InvalidInterfaceException
1035
+	 * @throws ReflectionException
1036
+	 */
1037
+	public function tax_total()
1038
+	{
1039
+		$tax_line_item = $this->tax_total_line_item();
1040
+		if ($tax_line_item) {
1041
+			return (float) $tax_line_item->total();
1042
+		}
1043
+		return (float) 0;
1044
+	}
1045
+
1046
+
1047
+	/**
1048
+	 * Gets the tax subtotal line item (assumes there's only one)
1049
+	 *
1050
+	 * @return EE_Line_Item
1051
+	 * @throws EE_Error
1052
+	 * @throws InvalidArgumentException
1053
+	 * @throws InvalidDataTypeException
1054
+	 * @throws InvalidInterfaceException
1055
+	 * @throws ReflectionException
1056
+	 */
1057
+	public function tax_total_line_item()
1058
+	{
1059
+		return EEH_Line_Item::get_taxes_subtotal($this->total_line_item());
1060
+	}
1061
+
1062
+
1063
+	/**
1064
+	 * Gets the array of billing info for the gateway and for this transaction's primary registration's attendee.
1065
+	 *
1066
+	 * @return EE_Form_Section_Proper
1067
+	 * @throws EE_Error
1068
+	 * @throws InvalidArgumentException
1069
+	 * @throws InvalidDataTypeException
1070
+	 * @throws InvalidInterfaceException
1071
+	 * @throws ReflectionException
1072
+	 */
1073
+	public function billing_info()
1074
+	{
1075
+		$payment_method = $this->payment_method();
1076
+		if (! $payment_method) {
1077
+			EE_Error::add_error(
1078
+				__(
1079
+					'Could not find billing info for transaction because no gateway has been used for it yet',
1080
+					'event_espresso'
1081
+				),
1082
+				__FILE__,
1083
+				__FUNCTION__,
1084
+				__LINE__
1085
+			);
1086
+			return null;
1087
+		}
1088
+		$primary_reg = $this->primary_registration();
1089
+		if (! $primary_reg) {
1090
+			EE_Error::add_error(
1091
+				__(
1092
+					'Cannot get billing info for gateway %s on transaction because no primary registration exists',
1093
+					'event_espresso'
1094
+				),
1095
+				__FILE__,
1096
+				__FUNCTION__,
1097
+				__LINE__
1098
+			);
1099
+			return null;
1100
+		}
1101
+		$attendee = $primary_reg->attendee();
1102
+		if (! $attendee) {
1103
+			EE_Error::add_error(
1104
+				__(
1105
+					'Cannot get billing info for gateway %s on transaction because the primary registration has no attendee exists',
1106
+					'event_espresso'
1107
+				),
1108
+				__FILE__,
1109
+				__FUNCTION__,
1110
+				__LINE__
1111
+			);
1112
+			return null;
1113
+		}
1114
+		return $attendee->billing_info_for_payment_method($payment_method);
1115
+	}
1116
+
1117
+
1118
+	/**
1119
+	 * Gets PMD_ID
1120
+	 *
1121
+	 * @return int
1122
+	 * @throws EE_Error
1123
+	 * @throws InvalidArgumentException
1124
+	 * @throws InvalidDataTypeException
1125
+	 * @throws InvalidInterfaceException
1126
+	 * @throws ReflectionException
1127
+	 */
1128
+	public function payment_method_ID()
1129
+	{
1130
+		return $this->get('PMD_ID');
1131
+	}
1132
+
1133
+
1134
+	/**
1135
+	 * Sets PMD_ID
1136
+	 *
1137
+	 * @param int $PMD_ID
1138
+	 * @throws EE_Error
1139
+	 * @throws InvalidArgumentException
1140
+	 * @throws InvalidDataTypeException
1141
+	 * @throws InvalidInterfaceException
1142
+	 * @throws ReflectionException
1143
+	 */
1144
+	public function set_payment_method_ID($PMD_ID)
1145
+	{
1146
+		$this->set('PMD_ID', $PMD_ID);
1147
+	}
1148
+
1149
+
1150
+	/**
1151
+	 * Gets the last-used payment method on this transaction
1152
+	 * (we COULD just use the last-made payment, but some payment methods, namely
1153
+	 * offline ones, dont' create payments)
1154
+	 *
1155
+	 * @return EE_Payment_Method
1156
+	 * @throws EE_Error
1157
+	 * @throws InvalidArgumentException
1158
+	 * @throws InvalidDataTypeException
1159
+	 * @throws InvalidInterfaceException
1160
+	 * @throws ReflectionException
1161
+	 */
1162
+	public function payment_method()
1163
+	{
1164
+		$pm = $this->get_first_related('Payment_Method');
1165
+		if ($pm instanceof EE_Payment_Method) {
1166
+			return $pm;
1167
+		}
1168
+		$last_payment = $this->last_payment();
1169
+		if ($last_payment instanceof EE_Payment && $last_payment->payment_method()) {
1170
+			return $last_payment->payment_method();
1171
+		}
1172
+		return null;
1173
+	}
1174
+
1175
+
1176
+	/**
1177
+	 * Gets the last payment made
1178
+	 *
1179
+	 * @return EE_Base_Class|EE_Payment
1180
+	 * @throws EE_Error
1181
+	 * @throws InvalidArgumentException
1182
+	 * @throws InvalidDataTypeException
1183
+	 * @throws InvalidInterfaceException
1184
+	 * @throws ReflectionException
1185
+	 */
1186
+	public function last_payment()
1187
+	{
1188
+		return $this->get_first_related('Payment', array('order_by' => array('PAY_ID' => 'desc')));
1189
+	}
1190
+
1191
+
1192
+	/**
1193
+	 * Gets all the line items which are unrelated to tickets on this transaction
1194
+	 *
1195
+	 * @return EE_Line_Item[]
1196
+	 * @throws EE_Error
1197
+	 * @throws InvalidArgumentException
1198
+	 * @throws InvalidDataTypeException
1199
+	 * @throws InvalidInterfaceException
1200
+	 * @throws ReflectionException
1201
+	 */
1202
+	public function non_ticket_line_items()
1203
+	{
1204
+		return EEM_Line_Item::instance()->get_all_non_ticket_line_items_for_transaction($this->ID());
1205
+	}
1206
+
1207
+
1208
+	/**
1209
+	 * possibly toggles TXN status
1210
+	 *
1211
+	 * @param  boolean $update whether to save the TXN
1212
+	 * @return bool whether the TXN was saved
1213
+	 * @throws EE_Error
1214
+	 * @throws InvalidArgumentException
1215
+	 * @throws InvalidDataTypeException
1216
+	 * @throws InvalidInterfaceException
1217
+	 * @throws ReflectionException
1218
+	 * @throws RuntimeException
1219
+	 */
1220
+	public function update_status_based_on_total_paid($update = true)
1221
+	{
1222
+		// set transaction status based on comparison of TXN_paid vs TXN_total
1223
+		if (EEH_Money::compare_floats($this->paid(), $this->total(), '>')) {
1224
+			$new_txn_status = EEM_Transaction::overpaid_status_code;
1225
+		} elseif (EEH_Money::compare_floats($this->paid(), $this->total())) {
1226
+			$new_txn_status = EEM_Transaction::complete_status_code;
1227
+		} elseif (EEH_Money::compare_floats($this->paid(), $this->total(), '<')) {
1228
+			$new_txn_status = EEM_Transaction::incomplete_status_code;
1229
+		} else {
1230
+			throw new RuntimeException(
1231
+				__('The total paid calculation for this transaction is inaccurate.', 'event_espresso')
1232
+			);
1233
+		}
1234
+		if ($new_txn_status !== $this->status_ID()) {
1235
+			$this->set_status($new_txn_status);
1236
+			if ($update) {
1237
+				return $this->save() ? true : false;
1238
+			}
1239
+		}
1240
+		return false;
1241
+	}
1242
+
1243
+
1244
+	/**
1245
+	 * Updates the transaction's status and total_paid based on all the payments
1246
+	 * that apply to it
1247
+	 *
1248
+	 * @deprecated
1249
+	 * @return array|bool
1250
+	 * @throws EE_Error
1251
+	 * @throws InvalidArgumentException
1252
+	 * @throws ReflectionException
1253
+	 * @throws InvalidDataTypeException
1254
+	 * @throws InvalidInterfaceException
1255
+	 */
1256
+	public function update_based_on_payments()
1257
+	{
1258
+		EE_Error::doing_it_wrong(
1259
+			__CLASS__ . '::' . __FUNCTION__,
1260
+			sprintf(
1261
+				__('This method is deprecated. Please use "%s" instead', 'event_espresso'),
1262
+				'EE_Transaction_Processor::update_transaction_and_registrations_after_checkout_or_payment()'
1263
+			),
1264
+			'4.6.0'
1265
+		);
1266
+		/** @type EE_Transaction_Processor $transaction_processor */
1267
+		$transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
1268
+		return $transaction_processor->update_transaction_and_registrations_after_checkout_or_payment($this);
1269
+	}
1270
+
1271
+
1272
+	/**
1273
+	 * @return string
1274
+	 */
1275
+	public function old_txn_status()
1276
+	{
1277
+		return $this->_old_txn_status;
1278
+	}
1279
+
1280
+
1281
+	/**
1282
+	 * @param string $old_txn_status
1283
+	 */
1284
+	public function set_old_txn_status($old_txn_status)
1285
+	{
1286
+		// only set the first time
1287
+		if ($this->_old_txn_status === null) {
1288
+			$this->_old_txn_status = $old_txn_status;
1289
+		}
1290
+	}
1291
+
1292
+
1293
+	/**
1294
+	 * reg_status_updated
1295
+	 *
1296
+	 * @return bool
1297
+	 * @throws EE_Error
1298
+	 * @throws InvalidArgumentException
1299
+	 * @throws InvalidDataTypeException
1300
+	 * @throws InvalidInterfaceException
1301
+	 * @throws ReflectionException
1302
+	 */
1303
+	public function txn_status_updated()
1304
+	{
1305
+		return $this->status_ID() !== $this->_old_txn_status && $this->_old_txn_status !== null;
1306
+	}
1307
+
1308
+
1309
+	/**
1310
+	 * _reg_steps_completed
1311
+	 * if $check_all is TRUE, then returns TRUE if ALL reg steps have been marked as completed,
1312
+	 * if a $reg_step_slug is provided, then this step will be skipped when testing for completion
1313
+	 * if $check_all is FALSE and a $reg_step_slug is provided, then ONLY that reg step will be tested for completion
1314
+	 *
1315
+	 * @param string $reg_step_slug
1316
+	 * @param bool   $check_all
1317
+	 * @return bool|int
1318
+	 * @throws EE_Error
1319
+	 * @throws InvalidArgumentException
1320
+	 * @throws InvalidDataTypeException
1321
+	 * @throws InvalidInterfaceException
1322
+	 * @throws ReflectionException
1323
+	 */
1324
+	private function _reg_steps_completed($reg_step_slug = '', $check_all = true)
1325
+	{
1326
+		$reg_steps = $this->reg_steps();
1327
+		if (! is_array($reg_steps) || empty($reg_steps)) {
1328
+			return false;
1329
+		}
1330
+		// loop thru reg steps array)
1331
+		foreach ($reg_steps as $slug => $reg_step_completed) {
1332
+			// if NOT checking ALL steps (only checking one step)
1333
+			if (! $check_all) {
1334
+				// and this is the one
1335
+				if ($slug === $reg_step_slug) {
1336
+					return $reg_step_completed;
1337
+				}
1338
+				// skip to next reg step in loop
1339
+				continue;
1340
+			}
1341
+			// $check_all must be true, else we would never have gotten to this point
1342
+			if ($slug === $reg_step_slug) {
1343
+				// if we reach this point, then we are testing either:
1344
+				// all_reg_steps_completed_except() or
1345
+				// all_reg_steps_completed_except_final_step(),
1346
+				// and since this is the reg step EXCEPTION being tested
1347
+				// we want to return true (yes true) if this reg step is NOT completed
1348
+				// ie: "is everything completed except the final step?"
1349
+				// "that is correct... the final step is not completed, but all others are."
1350
+				return $reg_step_completed !== true;
1351
+			}
1352
+			if ($reg_step_completed !== true) {
1353
+				// if any reg step is NOT completed, then ALL steps are not completed
1354
+				return false;
1355
+			}
1356
+		}
1357
+		return true;
1358
+	}
1359
+
1360
+
1361
+	/**
1362
+	 * all_reg_steps_completed
1363
+	 * returns:
1364
+	 *    true if ALL reg steps have been marked as completed
1365
+	 *        or false if any step is not completed
1366
+	 *
1367
+	 * @return bool
1368
+	 * @throws EE_Error
1369
+	 * @throws InvalidArgumentException
1370
+	 * @throws InvalidDataTypeException
1371
+	 * @throws InvalidInterfaceException
1372
+	 * @throws ReflectionException
1373
+	 */
1374
+	public function all_reg_steps_completed()
1375
+	{
1376
+		return $this->_reg_steps_completed();
1377
+	}
1378
+
1379
+
1380
+	/**
1381
+	 * all_reg_steps_completed_except
1382
+	 * returns:
1383
+	 *        true if ALL reg steps, except a particular step that you wish to skip over, have been marked as completed
1384
+	 *        or false if any other step is not completed
1385
+	 *        or false if ALL steps are completed including the exception you are testing !!!
1386
+	 *
1387
+	 * @param string $exception
1388
+	 * @return bool
1389
+	 * @throws EE_Error
1390
+	 * @throws InvalidArgumentException
1391
+	 * @throws InvalidDataTypeException
1392
+	 * @throws InvalidInterfaceException
1393
+	 * @throws ReflectionException
1394
+	 */
1395
+	public function all_reg_steps_completed_except($exception = '')
1396
+	{
1397
+		return $this->_reg_steps_completed($exception);
1398
+	}
1399
+
1400
+
1401
+	/**
1402
+	 * all_reg_steps_completed_except
1403
+	 * returns:
1404
+	 *        true if ALL reg steps, except the final step, have been marked as completed
1405
+	 *        or false if any step is not completed
1406
+	 *    or false if ALL steps are completed including the final step !!!
1407
+	 *
1408
+	 * @return bool
1409
+	 * @throws EE_Error
1410
+	 * @throws InvalidArgumentException
1411
+	 * @throws InvalidDataTypeException
1412
+	 * @throws InvalidInterfaceException
1413
+	 * @throws ReflectionException
1414
+	 */
1415
+	public function all_reg_steps_completed_except_final_step()
1416
+	{
1417
+		return $this->_reg_steps_completed('finalize_registration');
1418
+	}
1419
+
1420
+
1421
+	/**
1422
+	 * reg_step_completed
1423
+	 * returns:
1424
+	 *    true if a specific reg step has been marked as completed
1425
+	 *    a Unix timestamp if it has been initialized but not yet completed,
1426
+	 *    or false if it has not yet been initialized
1427
+	 *
1428
+	 * @param string $reg_step_slug
1429
+	 * @return bool|int
1430
+	 * @throws EE_Error
1431
+	 * @throws InvalidArgumentException
1432
+	 * @throws InvalidDataTypeException
1433
+	 * @throws InvalidInterfaceException
1434
+	 * @throws ReflectionException
1435
+	 */
1436
+	public function reg_step_completed($reg_step_slug)
1437
+	{
1438
+		return $this->_reg_steps_completed($reg_step_slug, false);
1439
+	}
1440
+
1441
+
1442
+	/**
1443
+	 * completed_final_reg_step
1444
+	 * returns:
1445
+	 *    true if the finalize_registration reg step has been marked as completed
1446
+	 *    a Unix timestamp if it has been initialized but not yet completed,
1447
+	 *    or false if it has not yet been initialized
1448
+	 *
1449
+	 * @return bool|int
1450
+	 * @throws EE_Error
1451
+	 * @throws InvalidArgumentException
1452
+	 * @throws InvalidDataTypeException
1453
+	 * @throws InvalidInterfaceException
1454
+	 * @throws ReflectionException
1455
+	 */
1456
+	public function final_reg_step_completed()
1457
+	{
1458
+		return $this->_reg_steps_completed('finalize_registration', false);
1459
+	}
1460
+
1461
+
1462
+	/**
1463
+	 * set_reg_step_initiated
1464
+	 * given a valid TXN_reg_step, this sets it's value to a unix timestamp
1465
+	 *
1466
+	 * @param string $reg_step_slug
1467
+	 * @return boolean
1468
+	 * @throws EE_Error
1469
+	 * @throws InvalidArgumentException
1470
+	 * @throws InvalidDataTypeException
1471
+	 * @throws InvalidInterfaceException
1472
+	 * @throws ReflectionException
1473
+	 */
1474
+	public function set_reg_step_initiated($reg_step_slug)
1475
+	{
1476
+		return $this->_set_reg_step_completed_status($reg_step_slug, time());
1477
+	}
1478
+
1479
+
1480
+	/**
1481
+	 * set_reg_step_completed
1482
+	 * given a valid TXN_reg_step, this sets the step as completed
1483
+	 *
1484
+	 * @param string $reg_step_slug
1485
+	 * @return boolean
1486
+	 * @throws EE_Error
1487
+	 * @throws InvalidArgumentException
1488
+	 * @throws InvalidDataTypeException
1489
+	 * @throws InvalidInterfaceException
1490
+	 * @throws ReflectionException
1491
+	 */
1492
+	public function set_reg_step_completed($reg_step_slug)
1493
+	{
1494
+		return $this->_set_reg_step_completed_status($reg_step_slug, true);
1495
+	}
1496
+
1497
+
1498
+	/**
1499
+	 * set_reg_step_completed
1500
+	 * given a valid TXN_reg_step slug, this sets the step as NOT completed
1501
+	 *
1502
+	 * @param string $reg_step_slug
1503
+	 * @return boolean
1504
+	 * @throws EE_Error
1505
+	 * @throws InvalidArgumentException
1506
+	 * @throws InvalidDataTypeException
1507
+	 * @throws InvalidInterfaceException
1508
+	 * @throws ReflectionException
1509
+	 */
1510
+	public function set_reg_step_not_completed($reg_step_slug)
1511
+	{
1512
+		return $this->_set_reg_step_completed_status($reg_step_slug, false);
1513
+	}
1514
+
1515
+
1516
+	/**
1517
+	 * set_reg_step_completed
1518
+	 * given a valid reg step slug, this sets the TXN_reg_step completed status which is either:
1519
+	 *
1520
+	 * @param  string      $reg_step_slug
1521
+	 * @param  boolean|int $status
1522
+	 * @return boolean
1523
+	 * @throws EE_Error
1524
+	 * @throws InvalidArgumentException
1525
+	 * @throws InvalidDataTypeException
1526
+	 * @throws InvalidInterfaceException
1527
+	 * @throws ReflectionException
1528
+	 */
1529
+	private function _set_reg_step_completed_status($reg_step_slug, $status)
1530
+	{
1531
+		// validate status
1532
+		$status = is_bool($status) || is_int($status) ? $status : false;
1533
+		// get reg steps array
1534
+		$txn_reg_steps = $this->reg_steps();
1535
+		// if reg step does NOT exist
1536
+		if (! isset($txn_reg_steps[ $reg_step_slug ])) {
1537
+			return false;
1538
+		}
1539
+		// if  we're trying to complete a step that is already completed
1540
+		if ($txn_reg_steps[ $reg_step_slug ] === true) {
1541
+			return true;
1542
+		}
1543
+		// if  we're trying to complete a step that hasn't even started
1544
+		if ($status === true && $txn_reg_steps[ $reg_step_slug ] === false) {
1545
+			return false;
1546
+		}
1547
+		// if current status value matches the incoming value (no change)
1548
+		// type casting as int means values should collapse to either 0, 1, or a timestamp like 1234567890
1549
+		if ((int) $txn_reg_steps[ $reg_step_slug ] === (int) $status) {
1550
+			// this will happen in cases where multiple AJAX requests occur during the same step
1551
+			return true;
1552
+		}
1553
+		// if we're trying to set a start time, but it has already been set...
1554
+		if (is_numeric($status) && is_numeric($txn_reg_steps[ $reg_step_slug ])) {
1555
+			// skip the update below, but don't return FALSE so that errors won't be displayed
1556
+			return true;
1557
+		}
1558
+		// update completed status
1559
+		$txn_reg_steps[ $reg_step_slug ] = $status;
1560
+		$this->set_reg_steps($txn_reg_steps);
1561
+		$this->save();
1562
+		return true;
1563
+	}
1564
+
1565
+
1566
+	/**
1567
+	 * remove_reg_step
1568
+	 * given a valid TXN_reg_step slug, this will remove (unset)
1569
+	 * the reg step from the TXN reg step array
1570
+	 *
1571
+	 * @param string $reg_step_slug
1572
+	 * @return void
1573
+	 * @throws EE_Error
1574
+	 * @throws InvalidArgumentException
1575
+	 * @throws InvalidDataTypeException
1576
+	 * @throws InvalidInterfaceException
1577
+	 * @throws ReflectionException
1578
+	 */
1579
+	public function remove_reg_step($reg_step_slug)
1580
+	{
1581
+		// get reg steps array
1582
+		$txn_reg_steps = $this->reg_steps();
1583
+		unset($txn_reg_steps[ $reg_step_slug ]);
1584
+		$this->set_reg_steps($txn_reg_steps);
1585
+	}
1586
+
1587
+
1588
+	/**
1589
+	 * toggle_failed_transaction_status
1590
+	 * upgrades a TXNs status from failed to abandoned,
1591
+	 * meaning that contact information has been captured for at least one registrant
1592
+	 *
1593
+	 * @param bool $save
1594
+	 * @return bool
1595
+	 * @throws EE_Error
1596
+	 * @throws InvalidArgumentException
1597
+	 * @throws InvalidDataTypeException
1598
+	 * @throws InvalidInterfaceException
1599
+	 * @throws ReflectionException
1600
+	 */
1601
+	public function toggle_failed_transaction_status($save = true)
1602
+	{
1603
+		// if TXN status is still set as "failed"...
1604
+		if ($this->status_ID() === EEM_Transaction::failed_status_code) {
1605
+			$this->set_status(EEM_Transaction::abandoned_status_code);
1606
+			if ($save) {
1607
+				$this->save();
1608
+			}
1609
+			return true;
1610
+		}
1611
+		return false;
1612
+	}
1613
+
1614
+
1615
+	/**
1616
+	 * toggle_abandoned_transaction_status
1617
+	 * upgrades a TXNs status from failed or abandoned to incomplete
1618
+	 *
1619
+	 * @return bool
1620
+	 * @throws EE_Error
1621
+	 * @throws InvalidArgumentException
1622
+	 * @throws InvalidDataTypeException
1623
+	 * @throws InvalidInterfaceException
1624
+	 * @throws ReflectionException
1625
+	 */
1626
+	public function toggle_abandoned_transaction_status()
1627
+	{
1628
+		// if TXN status has not been updated already due to a payment, and is still set as "failed" or "abandoned"...
1629
+		$txn_status = $this->status_ID();
1630
+		if ($txn_status === EEM_Transaction::failed_status_code
1631
+			|| $txn_status === EEM_Transaction::abandoned_status_code
1632
+		) {
1633
+			// if a contact record for the primary registrant has been created
1634
+			if ($this->primary_registration() instanceof EE_Registration
1635
+				&& $this->primary_registration()->attendee() instanceof EE_Attendee
1636
+			) {
1637
+				$this->set_status(EEM_Transaction::incomplete_status_code);
1638
+			} else {
1639
+				// no contact record? yer abandoned!
1640
+				$this->set_status(EEM_Transaction::abandoned_status_code);
1641
+			}
1642
+			return true;
1643
+		}
1644
+		return false;
1645
+	}
1646
+
1647
+
1648
+	/**
1649
+	 * checks if an Abandoned TXN has any related payments, and if so,
1650
+	 * updates the TXN status based on the amount paid
1651
+	 *
1652
+	 * @throws EE_Error
1653
+	 * @throws InvalidDataTypeException
1654
+	 * @throws InvalidInterfaceException
1655
+	 * @throws InvalidArgumentException
1656
+	 * @throws RuntimeException
1657
+	 * @throws ReflectionException
1658
+	 */
1659
+	public function verify_abandoned_transaction_status()
1660
+	{
1661
+		if ($this->status_ID() !== EEM_Transaction::abandoned_status_code) {
1662
+			return;
1663
+		}
1664
+		$payments = $this->get_many_related('Payment');
1665
+		if (! empty($payments)) {
1666
+			foreach ($payments as $payment) {
1667
+				if ($payment instanceof EE_Payment) {
1668
+					// kk this TXN should NOT be abandoned
1669
+					$this->update_status_based_on_total_paid();
1670
+					if (! (defined('DOING_AJAX') && DOING_AJAX) && is_admin()) {
1671
+						EE_Error::add_attention(
1672
+							sprintf(
1673
+								esc_html__(
1674
+									'The status for Transaction #%1$d has been updated from "Abandoned" to "%2$s", because at least one payment has been made towards it. If the payment appears in the "Payment Details" table below, you may need to edit its status and/or other details as well.',
1675
+									'event_espresso'
1676
+								),
1677
+								$this->ID(),
1678
+								$this->pretty_status()
1679
+							)
1680
+						);
1681
+					}
1682
+					// get final reg step status
1683
+					$finalized = $this->final_reg_step_completed();
1684
+					// if the 'finalize_registration' step has been initiated (has a timestamp)
1685
+					// but has not yet been fully completed (TRUE)
1686
+					if (is_int($finalized) && $finalized !== false && $finalized !== true) {
1687
+						$this->set_reg_step_completed('finalize_registration');
1688
+						$this->save();
1689
+					}
1690
+				}
1691
+			}
1692
+		}
1693
+	}
1694 1694
 }
Please login to merge, or discard this patch.
admin_pages/transactions/Transactions_Admin_Page.core.php 1 patch
Indentation   +2564 added lines, -2564 removed lines patch added patch discarded remove patch
@@ -13,2568 +13,2568 @@
 block discarded – undo
13 13
 class Transactions_Admin_Page extends EE_Admin_Page
14 14
 {
15 15
 
16
-    /**
17
-     * @var EE_Transaction
18
-     */
19
-    private $_transaction;
20
-
21
-    /**
22
-     * @var EE_Session
23
-     */
24
-    private $_session;
25
-
26
-    /**
27
-     * @var array $_txn_status
28
-     */
29
-    private static $_txn_status;
30
-
31
-    /**
32
-     * @var array $_pay_status
33
-     */
34
-    private static $_pay_status;
35
-
36
-    /**
37
-     * @var array $_existing_reg_payment_REG_IDs
38
-     */
39
-    protected $_existing_reg_payment_REG_IDs;
40
-
41
-
42
-    /**
43
-     *    _init_page_props
44
-     *
45
-     * @return void
46
-     */
47
-    protected function _init_page_props()
48
-    {
49
-        $this->page_slug = TXN_PG_SLUG;
50
-        $this->page_label = esc_html__('Transactions', 'event_espresso');
51
-        $this->_admin_base_url = TXN_ADMIN_URL;
52
-        $this->_admin_base_path = TXN_ADMIN;
53
-    }
54
-
55
-
56
-    /**
57
-     *    _ajax_hooks
58
-     *
59
-     * @return void
60
-     */
61
-    protected function _ajax_hooks()
62
-    {
63
-        add_action('wp_ajax_espresso_apply_payment', array($this, 'apply_payments_or_refunds'));
64
-        add_action('wp_ajax_espresso_apply_refund', array($this, 'apply_payments_or_refunds'));
65
-        add_action('wp_ajax_espresso_delete_payment', array($this, 'delete_payment'));
66
-    }
67
-
68
-
69
-    /**
70
-     *    _define_page_props
71
-     *
72
-     * @return void
73
-     */
74
-    protected function _define_page_props()
75
-    {
76
-        $this->_admin_page_title = $this->page_label;
77
-        $this->_labels = array(
78
-            'buttons' => array(
79
-                'add'    => esc_html__('Add New Transaction', 'event_espresso'),
80
-                'edit'   => esc_html__('Edit Transaction', 'event_espresso'),
81
-                'delete' => esc_html__('Delete Transaction', 'event_espresso'),
82
-            ),
83
-        );
84
-    }
85
-
86
-
87
-    /**
88
-     *        grab url requests and route them
89
-     *
90
-     * @access private
91
-     * @return void
92
-     * @throws EE_Error
93
-     * @throws InvalidArgumentException
94
-     * @throws InvalidDataTypeException
95
-     * @throws InvalidInterfaceException
96
-     */
97
-    public function _set_page_routes()
98
-    {
99
-
100
-        $this->_set_transaction_status_array();
101
-
102
-        $txn_id = ! empty($this->_req_data['TXN_ID'])
103
-                  && ! is_array($this->_req_data['TXN_ID'])
104
-            ? $this->_req_data['TXN_ID']
105
-            : 0;
106
-
107
-        $this->_page_routes = array(
108
-
109
-            'default' => array(
110
-                'func'       => '_transactions_overview_list_table',
111
-                'capability' => 'ee_read_transactions',
112
-            ),
113
-
114
-            'view_transaction' => array(
115
-                'func'       => '_transaction_details',
116
-                'capability' => 'ee_read_transaction',
117
-                'obj_id'     => $txn_id,
118
-            ),
119
-
120
-            'send_payment_reminder' => array(
121
-                'func'       => '_send_payment_reminder',
122
-                'noheader'   => true,
123
-                'capability' => 'ee_send_message',
124
-            ),
125
-
126
-            'espresso_apply_payment' => array(
127
-                'func'       => 'apply_payments_or_refunds',
128
-                'noheader'   => true,
129
-                'capability' => 'ee_edit_payments',
130
-            ),
131
-
132
-            'espresso_apply_refund' => array(
133
-                'func'       => 'apply_payments_or_refunds',
134
-                'noheader'   => true,
135
-                'capability' => 'ee_edit_payments',
136
-            ),
137
-
138
-            'espresso_delete_payment' => array(
139
-                'func'       => 'delete_payment',
140
-                'noheader'   => true,
141
-                'capability' => 'ee_delete_payments',
142
-            ),
143
-
144
-            'espresso_recalculate_line_items' => array(
145
-                'func'       => 'recalculateLineItems',
146
-                'noheader'   => true,
147
-                'capability' => 'ee_edit_payments',
148
-            ),
149
-
150
-        );
151
-    }
152
-
153
-
154
-    protected function _set_page_config()
155
-    {
156
-        $this->_page_config = array(
157
-            'default'          => array(
158
-                'nav'           => array(
159
-                    'label' => esc_html__('Overview', 'event_espresso'),
160
-                    'order' => 10,
161
-                ),
162
-                'list_table'    => 'EE_Admin_Transactions_List_Table',
163
-                'help_tabs'     => array(
164
-                    'transactions_overview_help_tab'                       => array(
165
-                        'title'    => esc_html__('Transactions Overview', 'event_espresso'),
166
-                        'filename' => 'transactions_overview',
167
-                    ),
168
-                    'transactions_overview_table_column_headings_help_tab' => array(
169
-                        'title'    => esc_html__('Transactions Table Column Headings', 'event_espresso'),
170
-                        'filename' => 'transactions_overview_table_column_headings',
171
-                    ),
172
-                    'transactions_overview_views_filters_help_tab'         => array(
173
-                        'title'    => esc_html__('Transaction Views & Filters & Search', 'event_espresso'),
174
-                        'filename' => 'transactions_overview_views_filters_search',
175
-                    ),
176
-                ),
177
-                'help_tour'     => array('Transactions_Overview_Help_Tour'),
178
-                /**
179
-                 * commented out because currently we are not displaying tips for transaction list table status but this
180
-                 * may change in a later iteration so want to keep the code for then.
181
-                 */
182
-                // 'qtips' => array( 'Transactions_List_Table_Tips' ),
183
-                'require_nonce' => false,
184
-            ),
185
-            'view_transaction' => array(
186
-                'nav'       => array(
187
-                    'label'      => esc_html__('View Transaction', 'event_espresso'),
188
-                    'order'      => 5,
189
-                    'url'        => isset($this->_req_data['TXN_ID'])
190
-                        ? add_query_arg(array('TXN_ID' => $this->_req_data['TXN_ID']), $this->_current_page_view_url)
191
-                        : $this->_admin_base_url,
192
-                    'persistent' => false,
193
-                ),
194
-                'help_tabs' => array(
195
-                    'transactions_view_transaction_help_tab'                                              => array(
196
-                        'title'    => esc_html__('View Transaction', 'event_espresso'),
197
-                        'filename' => 'transactions_view_transaction',
198
-                    ),
199
-                    'transactions_view_transaction_transaction_details_table_help_tab'                    => array(
200
-                        'title'    => esc_html__('Transaction Details Table', 'event_espresso'),
201
-                        'filename' => 'transactions_view_transaction_transaction_details_table',
202
-                    ),
203
-                    'transactions_view_transaction_attendees_registered_help_tab'                         => array(
204
-                        'title'    => esc_html__('Attendees Registered', 'event_espresso'),
205
-                        'filename' => 'transactions_view_transaction_attendees_registered',
206
-                    ),
207
-                    'transactions_view_transaction_views_primary_registrant_billing_information_help_tab' => array(
208
-                        'title'    => esc_html__('Primary Registrant & Billing Information', 'event_espresso'),
209
-                        'filename' => 'transactions_view_transaction_primary_registrant_billing_information',
210
-                    ),
211
-                ),
212
-                'qtips'     => array('Transaction_Details_Tips'),
213
-                'help_tour' => array('Transaction_Details_Help_Tour'),
214
-                'metaboxes' => array('_transaction_details_metaboxes'),
215
-
216
-                'require_nonce' => false,
217
-            ),
218
-        );
219
-    }
220
-
221
-
222
-    /**
223
-     * The below methods aren't used by this class currently
224
-     */
225
-    protected function _add_screen_options()
226
-    {
227
-        // noop
228
-    }
229
-
230
-
231
-    protected function _add_feature_pointers()
232
-    {
233
-        // noop
234
-    }
235
-
236
-
237
-    public function admin_init()
238
-    {
239
-        // IF a registration was JUST added via the admin...
240
-        if (isset(
241
-            $this->_req_data['redirect_from'],
242
-            $this->_req_data['EVT_ID'],
243
-            $this->_req_data['event_name']
244
-        )) {
245
-            // then set a cookie so that we can block any attempts to use
246
-            // the back button as a way to enter another registration.
247
-            setcookie(
248
-                'ee_registration_added',
249
-                $this->_req_data['EVT_ID'],
250
-                time() + WEEK_IN_SECONDS,
251
-                '/'
252
-            );
253
-            // and update the global
254
-            $_COOKIE['ee_registration_added'] = $this->_req_data['EVT_ID'];
255
-        }
256
-        EE_Registry::$i18n_js_strings['invalid_server_response'] = esc_html__(
257
-            'An error occurred! Your request may have been processed, but a valid response from the server was not received. Please refresh the page and try again.',
258
-            'event_espresso'
259
-        );
260
-        EE_Registry::$i18n_js_strings['error_occurred'] = esc_html__(
261
-            'An error occurred! Please refresh the page and try again.',
262
-            'event_espresso'
263
-        );
264
-        EE_Registry::$i18n_js_strings['txn_status_array'] = self::$_txn_status;
265
-        EE_Registry::$i18n_js_strings['pay_status_array'] = self::$_pay_status;
266
-        EE_Registry::$i18n_js_strings['payments_total'] = esc_html__('Payments Total', 'event_espresso');
267
-        EE_Registry::$i18n_js_strings['transaction_overpaid'] = esc_html__(
268
-            'This transaction has been overpaid ! Payments Total',
269
-            'event_espresso'
270
-        );
271
-    }
272
-
273
-
274
-    public function admin_notices()
275
-    {
276
-        // noop
277
-    }
278
-
279
-
280
-    public function admin_footer_scripts()
281
-    {
282
-        // noop
283
-    }
284
-
285
-
286
-    /**
287
-     * _set_transaction_status_array
288
-     * sets list of transaction statuses
289
-     *
290
-     * @access private
291
-     * @return void
292
-     * @throws EE_Error
293
-     * @throws InvalidArgumentException
294
-     * @throws InvalidDataTypeException
295
-     * @throws InvalidInterfaceException
296
-     */
297
-    private function _set_transaction_status_array()
298
-    {
299
-        self::$_txn_status = EEM_Transaction::instance()->status_array(true);
300
-    }
301
-
302
-
303
-    /**
304
-     * get_transaction_status_array
305
-     * return the transaction status array for wp_list_table
306
-     *
307
-     * @access public
308
-     * @return array
309
-     */
310
-    public function get_transaction_status_array()
311
-    {
312
-        return self::$_txn_status;
313
-    }
314
-
315
-
316
-    /**
317
-     *    get list of payment statuses
318
-     *
319
-     * @access private
320
-     * @return void
321
-     * @throws EE_Error
322
-     * @throws InvalidArgumentException
323
-     * @throws InvalidDataTypeException
324
-     * @throws InvalidInterfaceException
325
-     */
326
-    private function _get_payment_status_array()
327
-    {
328
-        self::$_pay_status = EEM_Payment::instance()->status_array(true);
329
-        $this->_template_args['payment_status'] = self::$_pay_status;
330
-    }
331
-
332
-
333
-    /**
334
-     *    _add_screen_options_default
335
-     *
336
-     * @access protected
337
-     * @return void
338
-     * @throws InvalidArgumentException
339
-     * @throws InvalidDataTypeException
340
-     * @throws InvalidInterfaceException
341
-     */
342
-    protected function _add_screen_options_default()
343
-    {
344
-        $this->_per_page_screen_option();
345
-    }
346
-
347
-
348
-    /**
349
-     * load_scripts_styles
350
-     *
351
-     * @access public
352
-     * @return void
353
-     */
354
-    public function load_scripts_styles()
355
-    {
356
-        // enqueue style
357
-        wp_register_style(
358
-            'espresso_txn',
359
-            TXN_ASSETS_URL . 'espresso_transactions_admin.css',
360
-            array(),
361
-            EVENT_ESPRESSO_VERSION
362
-        );
363
-        wp_enqueue_style('espresso_txn');
364
-        // scripts
365
-        wp_register_script(
366
-            'espresso_txn',
367
-            TXN_ASSETS_URL . 'espresso_transactions_admin.js',
368
-            array(
369
-                'ee_admin_js',
370
-                'ee-datepicker',
371
-                'jquery-ui-datepicker',
372
-                'jquery-ui-draggable',
373
-                'ee-dialog',
374
-                'ee-accounting',
375
-                'ee-serialize-full-array',
376
-            ),
377
-            EVENT_ESPRESSO_VERSION,
378
-            true
379
-        );
380
-        wp_enqueue_script('espresso_txn');
381
-    }
382
-
383
-
384
-    /**
385
-     *    load_scripts_styles_view_transaction
386
-     *
387
-     * @access public
388
-     * @return void
389
-     */
390
-    public function load_scripts_styles_view_transaction()
391
-    {
392
-        // styles
393
-        wp_enqueue_style('espresso-ui-theme');
394
-    }
395
-
396
-
397
-    /**
398
-     *    load_scripts_styles_default
399
-     *
400
-     * @access public
401
-     * @return void
402
-     */
403
-    public function load_scripts_styles_default()
404
-    {
405
-        // styles
406
-        wp_enqueue_style('espresso-ui-theme');
407
-    }
408
-
409
-
410
-    /**
411
-     *    _set_list_table_views_default
412
-     *
413
-     * @access protected
414
-     * @return void
415
-     */
416
-    protected function _set_list_table_views_default()
417
-    {
418
-        $this->_views = array(
419
-            'all'        => array(
420
-                'slug'  => 'all',
421
-                'label' => esc_html__('View All Transactions', 'event_espresso'),
422
-                'count' => 0,
423
-            ),
424
-            'abandoned'  => array(
425
-                'slug'  => 'abandoned',
426
-                'label' => esc_html__('Abandoned Transactions', 'event_espresso'),
427
-                'count' => 0,
428
-            ),
429
-            'incomplete' => array(
430
-                'slug'  => 'incomplete',
431
-                'label' => esc_html__('Incomplete Transactions', 'event_espresso'),
432
-                'count' => 0,
433
-            ),
434
-        );
435
-        if (/**
436
-         * Filters whether a link to the "Failed Transactions" list table
437
-         * appears on the Transactions Admin Page list table.
438
-         * List display can be turned back on via the following:
439
-         * add_filter(
440
-         *     'FHEE__Transactions_Admin_Page___set_list_table_views_default__display_failed_txns_list',
441
-         *     '__return_true'
442
-         * );
443
-         *
444
-         * @since 4.9.70.p
445
-         * @param boolean                 $display_failed_txns_list
446
-         * @param Transactions_Admin_Page $this
447
-         */
448
-        apply_filters(
449
-            'FHEE__Transactions_Admin_Page___set_list_table_views_default__display_failed_txns_list',
450
-            false,
451
-            $this
452
-        )
453
-        ) {
454
-            $this->_views['failed'] = array(
455
-                'slug'  => 'failed',
456
-                'label' => esc_html__('Failed Transactions', 'event_espresso'),
457
-                'count' => 0,
458
-            );
459
-        }
460
-    }
461
-
462
-
463
-    /**
464
-     * _set_transaction_object
465
-     * This sets the _transaction property for the transaction details screen
466
-     *
467
-     * @access private
468
-     * @return void
469
-     * @throws EE_Error
470
-     * @throws InvalidArgumentException
471
-     * @throws RuntimeException
472
-     * @throws InvalidDataTypeException
473
-     * @throws InvalidInterfaceException
474
-     * @throws ReflectionException
475
-     */
476
-    private function _set_transaction_object()
477
-    {
478
-        if ($this->_transaction instanceof EE_Transaction) {
479
-            return;
480
-        } //get out we've already set the object
481
-
482
-        $TXN_ID = ! empty($this->_req_data['TXN_ID'])
483
-            ? absint($this->_req_data['TXN_ID'])
484
-            : false;
485
-
486
-        // get transaction object
487
-        $this->_transaction = EEM_Transaction::instance()->get_one_by_ID($TXN_ID);
488
-        $this->_session = $this->_transaction instanceof EE_Transaction
489
-            ? $this->_transaction->session_data()
490
-            : null;
491
-        if ($this->_transaction instanceof EE_Transaction) {
492
-            $this->_transaction->verify_abandoned_transaction_status();
493
-        }
494
-
495
-        if (! $this->_transaction instanceof EE_Transaction) {
496
-            $error_msg = sprintf(
497
-                esc_html__(
498
-                    'An error occurred and the details for the transaction with the ID # %d could not be retrieved.',
499
-                    'event_espresso'
500
-                ),
501
-                $TXN_ID
502
-            );
503
-            EE_Error::add_error($error_msg, __FILE__, __FUNCTION__, __LINE__);
504
-        }
505
-    }
506
-
507
-
508
-    /**
509
-     *    _transaction_legend_items
510
-     *
511
-     * @access protected
512
-     * @return array
513
-     * @throws EE_Error
514
-     * @throws InvalidArgumentException
515
-     * @throws ReflectionException
516
-     * @throws InvalidDataTypeException
517
-     * @throws InvalidInterfaceException
518
-     */
519
-    protected function _transaction_legend_items()
520
-    {
521
-        EE_Registry::instance()->load_helper('MSG_Template');
522
-        $items = array();
523
-
524
-        if (EE_Registry::instance()->CAP->current_user_can(
525
-            'ee_read_global_messages',
526
-            'view_filtered_messages'
527
-        )) {
528
-            $related_for_icon = EEH_MSG_Template::get_message_action_icon('see_notifications_for');
529
-            if (is_array($related_for_icon)
530
-                && isset($related_for_icon['css_class'], $related_for_icon['label'])
531
-            ) {
532
-                $items['view_related_messages'] = array(
533
-                    'class' => $related_for_icon['css_class'],
534
-                    'desc'  => $related_for_icon['label'],
535
-                );
536
-            }
537
-        }
538
-
539
-        $items = apply_filters(
540
-            'FHEE__Transactions_Admin_Page___transaction_legend_items__items',
541
-            array_merge(
542
-                $items,
543
-                array(
544
-                    'view_details'          => array(
545
-                        'class' => 'dashicons dashicons-cart',
546
-                        'desc'  => esc_html__('View Transaction Details', 'event_espresso'),
547
-                    ),
548
-                    'view_invoice'          => array(
549
-                        'class' => 'dashicons dashicons-media-spreadsheet',
550
-                        'desc'  => esc_html__('View Transaction Invoice', 'event_espresso'),
551
-                    ),
552
-                    'view_receipt'          => array(
553
-                        'class' => 'dashicons dashicons-media-default',
554
-                        'desc'  => esc_html__('View Transaction Receipt', 'event_espresso'),
555
-                    ),
556
-                    'view_registration'     => array(
557
-                        'class' => 'dashicons dashicons-clipboard',
558
-                        'desc'  => esc_html__('View Registration Details', 'event_espresso'),
559
-                    ),
560
-                    'payment_overview_link' => array(
561
-                        'class' => 'dashicons dashicons-money',
562
-                        'desc'  => esc_html__('Make Payment on Frontend', 'event_espresso'),
563
-                    ),
564
-                )
565
-            )
566
-        );
567
-
568
-        if (EEH_MSG_Template::is_mt_active('payment_reminder')
569
-            && EE_Registry::instance()->CAP->current_user_can(
570
-                'ee_send_message',
571
-                'espresso_transactions_send_payment_reminder'
572
-            )
573
-        ) {
574
-            $items['send_payment_reminder'] = array(
575
-                'class' => 'dashicons dashicons-email-alt',
576
-                'desc'  => esc_html__('Send Payment Reminder', 'event_espresso'),
577
-            );
578
-        } else {
579
-            $items['blank*'] = array(
580
-                'class' => '',
581
-                'desc'  => '',
582
-            );
583
-        }
584
-        $more_items = apply_filters(
585
-            'FHEE__Transactions_Admin_Page___transaction_legend_items__more_items',
586
-            array(
587
-                'overpaid'   => array(
588
-                    'class' => 'ee-status-legend ee-status-legend-' . EEM_Transaction::overpaid_status_code,
589
-                    'desc'  => EEH_Template::pretty_status(
590
-                        EEM_Transaction::overpaid_status_code,
591
-                        false,
592
-                        'sentence'
593
-                    ),
594
-                ),
595
-                'complete'   => array(
596
-                    'class' => 'ee-status-legend ee-status-legend-' . EEM_Transaction::complete_status_code,
597
-                    'desc'  => EEH_Template::pretty_status(
598
-                        EEM_Transaction::complete_status_code,
599
-                        false,
600
-                        'sentence'
601
-                    ),
602
-                ),
603
-                'incomplete' => array(
604
-                    'class' => 'ee-status-legend ee-status-legend-' . EEM_Transaction::incomplete_status_code,
605
-                    'desc'  => EEH_Template::pretty_status(
606
-                        EEM_Transaction::incomplete_status_code,
607
-                        false,
608
-                        'sentence'
609
-                    ),
610
-                ),
611
-                'abandoned'  => array(
612
-                    'class' => 'ee-status-legend ee-status-legend-' . EEM_Transaction::abandoned_status_code,
613
-                    'desc'  => EEH_Template::pretty_status(
614
-                        EEM_Transaction::abandoned_status_code,
615
-                        false,
616
-                        'sentence'
617
-                    ),
618
-                ),
619
-                'failed'     => array(
620
-                    'class' => 'ee-status-legend ee-status-legend-' . EEM_Transaction::failed_status_code,
621
-                    'desc'  => EEH_Template::pretty_status(
622
-                        EEM_Transaction::failed_status_code,
623
-                        false,
624
-                        'sentence'
625
-                    ),
626
-                ),
627
-            )
628
-        );
629
-
630
-        return array_merge($items, $more_items);
631
-    }
632
-
633
-
634
-    /**
635
-     *    _transactions_overview_list_table
636
-     *
637
-     * @access protected
638
-     * @return void
639
-     * @throws DomainException
640
-     * @throws EE_Error
641
-     * @throws InvalidArgumentException
642
-     * @throws InvalidDataTypeException
643
-     * @throws InvalidInterfaceException
644
-     * @throws ReflectionException
645
-     */
646
-    protected function _transactions_overview_list_table()
647
-    {
648
-        $this->_admin_page_title = esc_html__('Transactions', 'event_espresso');
649
-        $event = isset($this->_req_data['EVT_ID'])
650
-            ? EEM_Event::instance()->get_one_by_ID($this->_req_data['EVT_ID'])
651
-            : null;
652
-        $this->_template_args['admin_page_header'] = $event instanceof EE_Event
653
-            ? sprintf(
654
-                esc_html__(
655
-                    '%sViewing Transactions for the Event: %s%s',
656
-                    'event_espresso'
657
-                ),
658
-                '<h3>',
659
-                '<a href="'
660
-                . EE_Admin_Page::add_query_args_and_nonce(
661
-                    array('action' => 'edit', 'post' => $event->ID()),
662
-                    EVENTS_ADMIN_URL
663
-                )
664
-                . '" title="'
665
-                . esc_attr__(
666
-                    'Click to Edit event',
667
-                    'event_espresso'
668
-                )
669
-                . '">' . $event->name() . '</a>',
670
-                '</h3>'
671
-            )
672
-            : '';
673
-        $this->_template_args['after_list_table'] = $this->_display_legend($this->_transaction_legend_items());
674
-        $this->display_admin_list_table_page_with_no_sidebar();
675
-    }
676
-
677
-
678
-    /**
679
-     *    _transaction_details
680
-     * generates HTML for the View Transaction Details Admin page
681
-     *
682
-     * @access protected
683
-     * @return void
684
-     * @throws DomainException
685
-     * @throws EE_Error
686
-     * @throws InvalidArgumentException
687
-     * @throws InvalidDataTypeException
688
-     * @throws InvalidInterfaceException
689
-     * @throws RuntimeException
690
-     * @throws ReflectionException
691
-     */
692
-    protected function _transaction_details()
693
-    {
694
-        do_action('AHEE__Transactions_Admin_Page__transaction_details__start', $this->_transaction);
695
-
696
-        $this->_set_transaction_status_array();
697
-
698
-        $this->_template_args = array();
699
-        $this->_template_args['transactions_page'] = $this->_wp_page_slug;
700
-
701
-        $this->_set_transaction_object();
702
-
703
-        if (! $this->_transaction instanceof EE_Transaction) {
704
-            return;
705
-        }
706
-        $primary_registration = $this->_transaction->primary_registration();
707
-        $attendee = $primary_registration instanceof EE_Registration
708
-            ? $primary_registration->attendee()
709
-            : null;
710
-
711
-        $this->_template_args['txn_nmbr']['value'] = $this->_transaction->ID();
712
-        $this->_template_args['txn_nmbr']['label'] = esc_html__('Transaction Number', 'event_espresso');
713
-
714
-        $this->_template_args['txn_datetime']['value'] = $this->_transaction->get_i18n_datetime('TXN_timestamp');
715
-        $this->_template_args['txn_datetime']['label'] = esc_html__('Date', 'event_espresso');
716
-
717
-        $this->_template_args['txn_status']['value'] = self::$_txn_status[ $this->_transaction->status_ID() ];
718
-        $this->_template_args['txn_status']['label'] = esc_html__('Transaction Status', 'event_espresso');
719
-        $this->_template_args['txn_status']['class'] = 'status-' . $this->_transaction->status_ID();
720
-
721
-        $this->_template_args['grand_total'] = $this->_transaction->total();
722
-        $this->_template_args['total_paid'] = $this->_transaction->paid();
723
-
724
-        $amount_due = $this->_transaction->total() - $this->_transaction->paid();
725
-        $this->_template_args['amount_due'] = EEH_Template::format_currency(
726
-            $amount_due,
727
-            true
728
-        );
729
-        if (EE_Registry::instance()->CFG->currency->sign_b4) {
730
-            $this->_template_args['amount_due'] = EE_Registry::instance()->CFG->currency->sign
731
-                                                  . $this->_template_args['amount_due'];
732
-        } else {
733
-            $this->_template_args['amount_due'] .= EE_Registry::instance()->CFG->currency->sign;
734
-        }
735
-        $this->_template_args['amount_due_class'] = '';
736
-
737
-        if ($this->_transaction->paid() === $this->_transaction->total()) {
738
-            // paid in full
739
-            $this->_template_args['amount_due'] = false;
740
-        } elseif ($this->_transaction->paid() > $this->_transaction->total()) {
741
-            // overpaid
742
-            $this->_template_args['amount_due_class'] = 'txn-overview-no-payment-spn';
743
-        } elseif ($this->_transaction->total() > (float) 0) {
744
-            if ($this->_transaction->paid() > (float) 0) {
745
-                // monies owing
746
-                $this->_template_args['amount_due_class'] = 'txn-overview-part-payment-spn';
747
-            } elseif ($this->_transaction->paid() === (float) 0) {
748
-                // no payments made yet
749
-                $this->_template_args['amount_due_class'] = 'txn-overview-no-payment-spn';
750
-            }
751
-        } elseif ($this->_transaction->total() === (float) 0) {
752
-            // free event
753
-            $this->_template_args['amount_due'] = false;
754
-        }
755
-
756
-        $payment_method = $this->_transaction->payment_method();
757
-
758
-        $this->_template_args['method_of_payment_name'] = $payment_method instanceof EE_Payment_Method
759
-            ? $payment_method->admin_name()
760
-            : esc_html__('Unknown', 'event_espresso');
761
-
762
-        $this->_template_args['currency_sign'] = EE_Registry::instance()->CFG->currency->sign;
763
-        // link back to overview
764
-        $this->_template_args['txn_overview_url'] = ! empty($_SERVER['HTTP_REFERER'])
765
-            ? $_SERVER['HTTP_REFERER']
766
-            : TXN_ADMIN_URL;
767
-
768
-
769
-        // next link
770
-        $next_txn = $this->_transaction->next(
771
-            null,
772
-            array(array('STS_ID' => array('!=', EEM_Transaction::failed_status_code))),
773
-            'TXN_ID'
774
-        );
775
-        $this->_template_args['next_transaction'] = $next_txn
776
-            ? $this->_next_link(
777
-                EE_Admin_Page::add_query_args_and_nonce(
778
-                    array('action' => 'view_transaction', 'TXN_ID' => $next_txn['TXN_ID']),
779
-                    TXN_ADMIN_URL
780
-                ),
781
-                'dashicons dashicons-arrow-right ee-icon-size-22'
782
-            )
783
-            : '';
784
-        // previous link
785
-        $previous_txn = $this->_transaction->previous(
786
-            null,
787
-            array(array('STS_ID' => array('!=', EEM_Transaction::failed_status_code))),
788
-            'TXN_ID'
789
-        );
790
-        $this->_template_args['previous_transaction'] = $previous_txn
791
-            ? $this->_previous_link(
792
-                EE_Admin_Page::add_query_args_and_nonce(
793
-                    array('action' => 'view_transaction', 'TXN_ID' => $previous_txn['TXN_ID']),
794
-                    TXN_ADMIN_URL
795
-                ),
796
-                'dashicons dashicons-arrow-left ee-icon-size-22'
797
-            )
798
-            : '';
799
-
800
-        // were we just redirected here after adding a new registration ???
801
-        if (isset(
802
-            $this->_req_data['redirect_from'],
803
-            $this->_req_data['EVT_ID'],
804
-            $this->_req_data['event_name']
805
-        )) {
806
-            if (EE_Registry::instance()->CAP->current_user_can(
807
-                'ee_edit_registrations',
808
-                'espresso_registrations_new_registration',
809
-                $this->_req_data['EVT_ID']
810
-            )) {
811
-                $this->_admin_page_title .= '<a id="add-new-registration" class="add-new-h2 button-primary" href="';
812
-                $this->_admin_page_title .= EE_Admin_Page::add_query_args_and_nonce(
813
-                    array(
814
-                        'page'     => 'espresso_registrations',
815
-                        'action'   => 'new_registration',
816
-                        'return'   => 'default',
817
-                        'TXN_ID'   => $this->_transaction->ID(),
818
-                        'event_id' => $this->_req_data['EVT_ID'],
819
-                    ),
820
-                    REG_ADMIN_URL
821
-                );
822
-                $this->_admin_page_title .= '">';
823
-
824
-                $this->_admin_page_title .= sprintf(
825
-                    esc_html__('Add Another New Registration to Event: "%1$s" ?', 'event_espresso'),
826
-                    htmlentities(urldecode($this->_req_data['event_name']), ENT_QUOTES, 'UTF-8')
827
-                );
828
-                $this->_admin_page_title .= '</a>';
829
-            }
830
-            EE_Registry::instance()->SSN->clear_session(__CLASS__, __FUNCTION__);
831
-        }
832
-        // grab messages at the last second
833
-        $this->_template_args['notices'] = EE_Error::get_notices();
834
-        // path to template
835
-        $template_path = TXN_TEMPLATE_PATH . 'txn_admin_details_header.template.php';
836
-        $this->_template_args['admin_page_header'] = EEH_Template::display_template(
837
-            $template_path,
838
-            $this->_template_args,
839
-            true
840
-        );
841
-
842
-        // the details template wrapper
843
-        $this->display_admin_page_with_sidebar();
844
-    }
845
-
846
-
847
-    /**
848
-     *        _transaction_details_metaboxes
849
-     *
850
-     * @access protected
851
-     * @return void
852
-     * @throws EE_Error
853
-     * @throws InvalidArgumentException
854
-     * @throws InvalidDataTypeException
855
-     * @throws InvalidInterfaceException
856
-     * @throws RuntimeException
857
-     * @throws ReflectionException
858
-     */
859
-    protected function _transaction_details_metaboxes()
860
-    {
861
-
862
-        $this->_set_transaction_object();
863
-
864
-        if (! $this->_transaction instanceof EE_Transaction) {
865
-            return;
866
-        }
867
-        add_meta_box(
868
-            'edit-txn-details-mbox',
869
-            esc_html__('Transaction Details', 'event_espresso'),
870
-            array($this, 'txn_details_meta_box'),
871
-            $this->_wp_page_slug,
872
-            'normal',
873
-            'high'
874
-        );
875
-        add_meta_box(
876
-            'edit-txn-attendees-mbox',
877
-            esc_html__('Attendees Registered in this Transaction', 'event_espresso'),
878
-            array($this, 'txn_attendees_meta_box'),
879
-            $this->_wp_page_slug,
880
-            'normal',
881
-            'high',
882
-            array('TXN_ID' => $this->_transaction->ID())
883
-        );
884
-        add_meta_box(
885
-            'edit-txn-registrant-mbox',
886
-            esc_html__('Primary Contact', 'event_espresso'),
887
-            array($this, 'txn_registrant_side_meta_box'),
888
-            $this->_wp_page_slug,
889
-            'side',
890
-            'high'
891
-        );
892
-        add_meta_box(
893
-            'edit-txn-billing-info-mbox',
894
-            esc_html__('Billing Information', 'event_espresso'),
895
-            array($this, 'txn_billing_info_side_meta_box'),
896
-            $this->_wp_page_slug,
897
-            'side',
898
-            'high'
899
-        );
900
-    }
901
-
902
-
903
-    /**
904
-     * Callback for transaction actions metabox.
905
-     *
906
-     * @param EE_Transaction|null $transaction
907
-     * @return string
908
-     * @throws DomainException
909
-     * @throws EE_Error
910
-     * @throws InvalidArgumentException
911
-     * @throws InvalidDataTypeException
912
-     * @throws InvalidInterfaceException
913
-     * @throws ReflectionException
914
-     * @throws RuntimeException
915
-     */
916
-    public function getActionButtons(EE_Transaction $transaction = null)
917
-    {
918
-        $content = '';
919
-        $actions = array();
920
-        if (! $transaction instanceof EE_Transaction) {
921
-            return $content;
922
-        }
923
-        /** @var EE_Registration $primary_registration */
924
-        $primary_registration = $transaction->primary_registration();
925
-        $attendee = $primary_registration instanceof EE_Registration
926
-            ? $primary_registration->attendee()
927
-            : null;
928
-
929
-        if ($attendee instanceof EE_Attendee
930
-            && EE_Registry::instance()->CAP->current_user_can(
931
-                'ee_send_message',
932
-                'espresso_transactions_send_payment_reminder'
933
-            )
934
-        ) {
935
-            $actions['payment_reminder'] =
936
-                EEH_MSG_Template::is_mt_active('payment_reminder')
937
-                && $this->_transaction->status_ID() !== EEM_Transaction::complete_status_code
938
-                && $this->_transaction->status_ID() !== EEM_Transaction::overpaid_status_code
939
-                    ? EEH_Template::get_button_or_link(
940
-                        EE_Admin_Page::add_query_args_and_nonce(
941
-                            array(
942
-                                'action'      => 'send_payment_reminder',
943
-                                'TXN_ID'      => $this->_transaction->ID(),
944
-                                'redirect_to' => 'view_transaction',
945
-                            ),
946
-                            TXN_ADMIN_URL
947
-                        ),
948
-                        esc_html__(' Send Payment Reminder', 'event_espresso'),
949
-                        'button secondary-button',
950
-                        'dashicons dashicons-email-alt'
951
-                    )
952
-                    : '';
953
-        }
954
-
955
-        if (EE_Registry::instance()->CAP->current_user_can(
956
-            'ee_edit_payments',
957
-            'espresso_transactions_recalculate_line_items'
958
-        )
959
-        ) {
960
-            $actions['recalculate_line_items'] = EEH_Template::get_button_or_link(
961
-                EE_Admin_Page::add_query_args_and_nonce(
962
-                    array(
963
-                        'action'      => 'espresso_recalculate_line_items',
964
-                        'TXN_ID'      => $this->_transaction->ID(),
965
-                        'redirect_to' => 'view_transaction',
966
-                    ),
967
-                    TXN_ADMIN_URL
968
-                ),
969
-                esc_html__(' Recalculate Taxes and Total', 'event_espresso'),
970
-                'button secondary-button',
971
-                'dashicons dashicons-update'
972
-            );
973
-        }
974
-
975
-        if ($primary_registration instanceof EE_Registration
976
-            && EEH_MSG_Template::is_mt_active('receipt')
977
-        ) {
978
-            $actions['receipt'] = EEH_Template::get_button_or_link(
979
-                $primary_registration->receipt_url(),
980
-                esc_html__('View Receipt', 'event_espresso'),
981
-                'button secondary-button',
982
-                'dashicons dashicons-media-default'
983
-            );
984
-        }
985
-
986
-        if ($primary_registration instanceof EE_Registration
987
-            && EEH_MSG_Template::is_mt_active('invoice')
988
-        ) {
989
-            $actions['invoice'] = EEH_Template::get_button_or_link(
990
-                $primary_registration->invoice_url(),
991
-                esc_html__('View Invoice', 'event_espresso'),
992
-                'button secondary-button',
993
-                'dashicons dashicons-media-spreadsheet'
994
-            );
995
-        }
996
-        $actions = array_filter(
997
-            apply_filters('FHEE__Transactions_Admin_Page__getActionButtons__actions', $actions, $transaction)
998
-        );
999
-        if ($actions) {
1000
-            $content = '<ul>';
1001
-            $content .= '<li>' . implode('</li><li>', $actions) . '</li>';
1002
-            $content .= '</uL>';
1003
-        }
1004
-        return $content;
1005
-    }
1006
-
1007
-
1008
-    /**
1009
-     * txn_details_meta_box
1010
-     * generates HTML for the Transaction main meta box
1011
-     *
1012
-     * @return void
1013
-     * @throws DomainException
1014
-     * @throws EE_Error
1015
-     * @throws InvalidArgumentException
1016
-     * @throws InvalidDataTypeException
1017
-     * @throws InvalidInterfaceException
1018
-     * @throws RuntimeException
1019
-     * @throws ReflectionException
1020
-     */
1021
-    public function txn_details_meta_box()
1022
-    {
1023
-        $this->_set_transaction_object();
1024
-        $this->_template_args['TXN_ID'] = $this->_transaction->ID();
1025
-        $this->_template_args['attendee'] = $this->_transaction->primary_registration() instanceof EE_Registration
1026
-            ? $this->_transaction->primary_registration()->attendee()
1027
-            : null;
1028
-        $this->_template_args['can_edit_payments'] = EE_Registry::instance()->CAP->current_user_can(
1029
-            'ee_edit_payments',
1030
-            'apply_payment_or_refund_from_registration_details'
1031
-        );
1032
-        $this->_template_args['can_delete_payments'] = EE_Registry::instance()->CAP->current_user_can(
1033
-            'ee_delete_payments',
1034
-            'delete_payment_from_registration_details'
1035
-        );
1036
-
1037
-        // get line table
1038
-        EEH_Autoloader::register_line_item_display_autoloaders();
1039
-        $Line_Item_Display = new EE_Line_Item_Display(
1040
-            'admin_table',
1041
-            'EE_Admin_Table_Line_Item_Display_Strategy'
1042
-        );
1043
-        $this->_template_args['line_item_table'] = $Line_Item_Display->display_line_item(
1044
-            $this->_transaction->total_line_item()
1045
-        );
1046
-        $this->_template_args['REG_code'] = $this->_transaction->primary_registration()->reg_code();
1047
-
1048
-        // process taxes
1049
-        $taxes = $this->_transaction->line_items(array(array('LIN_type' => EEM_Line_Item::type_tax)));
1050
-        $this->_template_args['taxes'] = ! empty($taxes) ? $taxes : false;
1051
-
1052
-        $this->_template_args['grand_total'] = EEH_Template::format_currency(
1053
-            $this->_transaction->total(),
1054
-            false,
1055
-            false
1056
-        );
1057
-        $this->_template_args['grand_raw_total'] = $this->_transaction->total();
1058
-        $this->_template_args['TXN_status'] = $this->_transaction->status_ID();
1059
-
1060
-        // process payment details
1061
-        $payments = $this->_transaction->payments();
1062
-        if (! empty($payments)) {
1063
-            $this->_template_args['payments'] = $payments;
1064
-            $this->_template_args['existing_reg_payments'] = $this->_get_registration_payment_IDs($payments);
1065
-        } else {
1066
-            $this->_template_args['payments'] = false;
1067
-            $this->_template_args['existing_reg_payments'] = array();
1068
-        }
1069
-
1070
-        $this->_template_args['edit_payment_url'] = add_query_arg(array('action' => 'edit_payment'), TXN_ADMIN_URL);
1071
-        $this->_template_args['delete_payment_url'] = add_query_arg(
1072
-            array('action' => 'espresso_delete_payment'),
1073
-            TXN_ADMIN_URL
1074
-        );
1075
-
1076
-        if (isset($txn_details['invoice_number'])) {
1077
-            $this->_template_args['txn_details']['invoice_number']['value'] = $this->_template_args['REG_code'];
1078
-            $this->_template_args['txn_details']['invoice_number']['label'] = esc_html__(
1079
-                'Invoice Number',
1080
-                'event_espresso'
1081
-            );
1082
-        }
1083
-
1084
-        $this->_template_args['txn_details']['registration_session']['value'] = $this->_transaction
1085
-            ->primary_registration()
1086
-            ->session_ID();
1087
-        $this->_template_args['txn_details']['registration_session']['label'] = esc_html__(
1088
-            'Registration Session',
1089
-            'event_espresso'
1090
-        );
1091
-
1092
-        $this->_template_args['txn_details']['ip_address']['value'] = isset($this->_session['ip_address'])
1093
-            ? $this->_session['ip_address']
1094
-            : '';
1095
-        $this->_template_args['txn_details']['ip_address']['label'] = esc_html__(
1096
-            'Transaction placed from IP',
1097
-            'event_espresso'
1098
-        );
1099
-
1100
-        $this->_template_args['txn_details']['user_agent']['value'] = isset($this->_session['user_agent'])
1101
-            ? $this->_session['user_agent']
1102
-            : '';
1103
-        $this->_template_args['txn_details']['user_agent']['label'] = esc_html__(
1104
-            'Registrant User Agent',
1105
-            'event_espresso'
1106
-        );
1107
-
1108
-        $reg_steps = '<ul>';
1109
-        foreach ($this->_transaction->reg_steps() as $reg_step => $reg_step_status) {
1110
-            if ($reg_step_status === true) {
1111
-                $reg_steps .= '<li style="color:#70cc50">'
1112
-                              . sprintf(
1113
-                                  esc_html__('%1$s : Completed', 'event_espresso'),
1114
-                                  ucwords(str_replace('_', ' ', $reg_step))
1115
-                              )
1116
-                              . '</li>';
1117
-            } elseif (is_numeric($reg_step_status) && $reg_step_status !== false) {
1118
-                $reg_steps .= '<li style="color:#2EA2CC">'
1119
-                              . sprintf(
1120
-                                  esc_html__('%1$s : Initiated %2$s', 'event_espresso'),
1121
-                                  ucwords(str_replace('_', ' ', $reg_step)),
1122
-                                  date(
1123
-                                      get_option('date_format') . ' ' . get_option('time_format'),
1124
-                                      $reg_step_status + (get_option('gmt_offset') * HOUR_IN_SECONDS)
1125
-                                  )
1126
-                              )
1127
-                              . '</li>';
1128
-            } else {
1129
-                $reg_steps .= '<li style="color:#E76700">'
1130
-                              . sprintf(
1131
-                                  esc_html__('%1$s : Never Initiated', 'event_espresso'),
1132
-                                  ucwords(str_replace('_', ' ', $reg_step))
1133
-                              )
1134
-                              . '</li>';
1135
-            }
1136
-        }
1137
-        $reg_steps .= '</ul>';
1138
-        $this->_template_args['txn_details']['reg_steps']['value'] = $reg_steps;
1139
-        $this->_template_args['txn_details']['reg_steps']['label'] = esc_html__(
1140
-            'Registration Step Progress',
1141
-            'event_espresso'
1142
-        );
1143
-
1144
-
1145
-        $this->_get_registrations_to_apply_payment_to();
1146
-        $this->_get_payment_methods($payments);
1147
-        $this->_get_payment_status_array();
1148
-        $this->_get_reg_status_selection(); // sets up the template args for the reg status array for the transaction.
1149
-
1150
-        $this->_template_args['transaction_form_url'] = add_query_arg(
1151
-            array(
1152
-                'action'  => 'edit_transaction',
1153
-                'process' => 'transaction',
1154
-            ),
1155
-            TXN_ADMIN_URL
1156
-        );
1157
-        $this->_template_args['apply_payment_form_url'] = add_query_arg(
1158
-            array(
1159
-                'page'   => 'espresso_transactions',
1160
-                'action' => 'espresso_apply_payment',
1161
-            ),
1162
-            WP_AJAX_URL
1163
-        );
1164
-        $this->_template_args['delete_payment_form_url'] = add_query_arg(
1165
-            array(
1166
-                'page'   => 'espresso_transactions',
1167
-                'action' => 'espresso_delete_payment',
1168
-            ),
1169
-            WP_AJAX_URL
1170
-        );
1171
-
1172
-        $this->_template_args['action_buttons'] = $this->getActionButtons($this->_transaction);
1173
-
1174
-        // 'espresso_delete_payment_nonce'
1175
-
1176
-        $template_path = TXN_TEMPLATE_PATH . 'txn_admin_details_main_meta_box_txn_details.template.php';
1177
-        echo EEH_Template::display_template($template_path, $this->_template_args, true);
1178
-    }
1179
-
1180
-
1181
-    /**
1182
-     * _get_registration_payment_IDs
1183
-     *    generates an array of Payment IDs and their corresponding Registration IDs
1184
-     *
1185
-     * @access protected
1186
-     * @param EE_Payment[] $payments
1187
-     * @return array
1188
-     * @throws EE_Error
1189
-     * @throws InvalidArgumentException
1190
-     * @throws InvalidDataTypeException
1191
-     * @throws InvalidInterfaceException
1192
-     * @throws ReflectionException
1193
-     */
1194
-    protected function _get_registration_payment_IDs($payments = array())
1195
-    {
1196
-        $existing_reg_payments = array();
1197
-        // get all reg payments for these payments
1198
-        $reg_payments = EEM_Registration_Payment::instance()->get_all(
1199
-            array(
1200
-                array(
1201
-                    'PAY_ID' => array(
1202
-                        'IN',
1203
-                        array_keys($payments),
1204
-                    ),
1205
-                ),
1206
-            )
1207
-        );
1208
-        if (! empty($reg_payments)) {
1209
-            foreach ($payments as $payment) {
1210
-                if (! $payment instanceof EE_Payment) {
1211
-                    continue;
1212
-                } elseif (! isset($existing_reg_payments[ $payment->ID() ])) {
1213
-                    $existing_reg_payments[ $payment->ID() ] = array();
1214
-                }
1215
-                foreach ($reg_payments as $reg_payment) {
1216
-                    if ($reg_payment instanceof EE_Registration_Payment
1217
-                        && $reg_payment->payment_ID() === $payment->ID()
1218
-                    ) {
1219
-                        $existing_reg_payments[ $payment->ID() ][] = $reg_payment->registration_ID();
1220
-                    }
1221
-                }
1222
-            }
1223
-        }
1224
-
1225
-        return $existing_reg_payments;
1226
-    }
1227
-
1228
-
1229
-    /**
1230
-     * _get_registrations_to_apply_payment_to
1231
-     *    generates HTML for displaying a series of checkboxes in the admin payment modal window
1232
-     * which allows the admin to only apply the payment to the specific registrations
1233
-     *
1234
-     * @access protected
1235
-     * @return void
1236
-     * @throws EE_Error
1237
-     * @throws InvalidArgumentException
1238
-     * @throws InvalidDataTypeException
1239
-     * @throws InvalidInterfaceException
1240
-     * @throws ReflectionException
1241
-     */
1242
-    protected function _get_registrations_to_apply_payment_to()
1243
-    {
1244
-        // we want any registration with an active status (ie: not deleted or cancelled)
1245
-        $query_params = array(
1246
-            array(
1247
-                'STS_ID' => array(
1248
-                    'IN',
1249
-                    array(
1250
-                        EEM_Registration::status_id_approved,
1251
-                        EEM_Registration::status_id_pending_payment,
1252
-                        EEM_Registration::status_id_not_approved,
1253
-                    ),
1254
-                ),
1255
-            ),
1256
-        );
1257
-        $registrations_to_apply_payment_to = EEH_HTML::br() . EEH_HTML::div(
1258
-            '',
1259
-            'txn-admin-apply-payment-to-registrations-dv',
1260
-            '',
1261
-            'clear: both; margin: 1.5em 0 0; display: none;'
1262
-        );
1263
-        $registrations_to_apply_payment_to .= EEH_HTML::br() . EEH_HTML::div('', '', 'admin-primary-mbox-tbl-wrap');
1264
-        $registrations_to_apply_payment_to .= EEH_HTML::table('', '', 'admin-primary-mbox-tbl');
1265
-        $registrations_to_apply_payment_to .= EEH_HTML::thead(
1266
-            EEH_HTML::tr(
1267
-                EEH_HTML::th(esc_html__('ID', 'event_espresso')) .
1268
-                EEH_HTML::th(esc_html__('Registrant', 'event_espresso')) .
1269
-                EEH_HTML::th(esc_html__('Ticket', 'event_espresso')) .
1270
-                EEH_HTML::th(esc_html__('Event', 'event_espresso')) .
1271
-                EEH_HTML::th(esc_html__('Paid', 'event_espresso'), '', 'txn-admin-payment-paid-td jst-cntr') .
1272
-                EEH_HTML::th(esc_html__('Owing', 'event_espresso'), '', 'txn-admin-payment-owing-td jst-cntr') .
1273
-                EEH_HTML::th(esc_html__('Apply', 'event_espresso'), '', 'jst-cntr')
1274
-            )
1275
-        );
1276
-        $registrations_to_apply_payment_to .= EEH_HTML::tbody();
1277
-        // get registrations for TXN
1278
-        $registrations = $this->_transaction->registrations($query_params);
1279
-        $existing_reg_payments = $this->_template_args['existing_reg_payments'];
1280
-        foreach ($registrations as $registration) {
1281
-            if ($registration instanceof EE_Registration) {
1282
-                $attendee_name = $registration->attendee() instanceof EE_Attendee
1283
-                    ? $registration->attendee()->full_name()
1284
-                    : esc_html__('Unknown Attendee', 'event_espresso');
1285
-                $owing = $registration->final_price() - $registration->paid();
1286
-                $taxable = $registration->ticket()->taxable()
1287
-                    ? ' <span class="smaller-text lt-grey-text"> ' . esc_html__('+ tax', 'event_espresso') . '</span>'
1288
-                    : '';
1289
-                $checked = empty($existing_reg_payments)
1290
-                           || in_array($registration->ID(), $existing_reg_payments, true)
1291
-                    ? ' checked="checked"'
1292
-                    : '';
1293
-                $disabled = $registration->final_price() > 0 ? '' : ' disabled';
1294
-                $registrations_to_apply_payment_to .= EEH_HTML::tr(
1295
-                    EEH_HTML::td($registration->ID()) .
1296
-                    EEH_HTML::td($attendee_name) .
1297
-                    EEH_HTML::td(
1298
-                        $registration->ticket()->name() . ' : ' . $registration->ticket()->pretty_price() . $taxable
1299
-                    ) .
1300
-                    EEH_HTML::td($registration->event_name()) .
1301
-                    EEH_HTML::td($registration->pretty_paid(), '', 'txn-admin-payment-paid-td jst-cntr') .
1302
-                    EEH_HTML::td(
1303
-                        EEH_Template::format_currency($owing),
1304
-                        '',
1305
-                        'txn-admin-payment-owing-td jst-cntr'
1306
-                    ) .
1307
-                    EEH_HTML::td(
1308
-                        '<input type="checkbox" value="' . $registration->ID()
1309
-                        . '" name="txn_admin_payment[registrations]"'
1310
-                        . $checked . $disabled . '>',
1311
-                        '',
1312
-                        'jst-cntr'
1313
-                    ),
1314
-                    'apply-payment-registration-row-' . $registration->ID()
1315
-                );
1316
-            }
1317
-        }
1318
-        $registrations_to_apply_payment_to .= EEH_HTML::tbodyx();
1319
-        $registrations_to_apply_payment_to .= EEH_HTML::tablex();
1320
-        $registrations_to_apply_payment_to .= EEH_HTML::divx();
1321
-        $registrations_to_apply_payment_to .= EEH_HTML::p(
1322
-            esc_html__(
1323
-                'The payment will only be applied to the registrations that have a check mark in their corresponding check box. Checkboxes for free registrations have been disabled.',
1324
-                'event_espresso'
1325
-            ),
1326
-            '',
1327
-            'clear description'
1328
-        );
1329
-        $registrations_to_apply_payment_to .= EEH_HTML::divx();
1330
-        $this->_template_args['registrations_to_apply_payment_to'] = $registrations_to_apply_payment_to;
1331
-    }
1332
-
1333
-
1334
-    /**
1335
-     * _get_reg_status_selection
1336
-     *
1337
-     * @todo   this will need to be adjusted either once MER comes along OR we move default reg status to tickets
1338
-     *         instead of events.
1339
-     * @access protected
1340
-     * @return void
1341
-     * @throws EE_Error
1342
-     */
1343
-    protected function _get_reg_status_selection()
1344
-    {
1345
-        // first get all possible statuses
1346
-        $statuses = EEM_Registration::reg_status_array(array(), true);
1347
-        // let's add a "don't change" option.
1348
-        $status_array['NAN'] = esc_html__('Leave the Same', 'event_espresso');
1349
-        $status_array = array_merge($status_array, $statuses);
1350
-        $this->_template_args['status_change_select'] = EEH_Form_Fields::select_input(
1351
-            'txn_reg_status_change[reg_status]',
1352
-            $status_array,
1353
-            'NAN',
1354
-            'id="txn-admin-payment-reg-status-inp"',
1355
-            'txn-reg-status-change-reg-status'
1356
-        );
1357
-        $this->_template_args['delete_status_change_select'] = EEH_Form_Fields::select_input(
1358
-            'delete_txn_reg_status_change[reg_status]',
1359
-            $status_array,
1360
-            'NAN',
1361
-            'delete-txn-admin-payment-reg-status-inp',
1362
-            'delete-txn-reg-status-change-reg-status'
1363
-        );
1364
-    }
1365
-
1366
-
1367
-    /**
1368
-     *    _get_payment_methods
1369
-     * Gets all the payment methods available generally, or the ones that are already
1370
-     * selected on these payments (in case their payment methods are no longer active).
1371
-     * Has the side-effect of updating the template args' payment_methods item
1372
-     *
1373
-     * @access private
1374
-     * @param EE_Payment[] to show on this page
1375
-     * @return void
1376
-     * @throws EE_Error
1377
-     * @throws InvalidArgumentException
1378
-     * @throws InvalidDataTypeException
1379
-     * @throws InvalidInterfaceException
1380
-     * @throws ReflectionException
1381
-     */
1382
-    private function _get_payment_methods($payments = array())
1383
-    {
1384
-        $payment_methods_of_payments = array();
1385
-        foreach ($payments as $payment) {
1386
-            if ($payment instanceof EE_Payment) {
1387
-                $payment_methods_of_payments[] = $payment->ID();
1388
-            }
1389
-        }
1390
-        if ($payment_methods_of_payments) {
1391
-            $query_args = array(
1392
-                array(
1393
-                    'OR*payment_method_for_payment' => array(
1394
-                        'PMD_ID'    => array('IN', $payment_methods_of_payments),
1395
-                        'PMD_scope' => array('LIKE', '%' . EEM_Payment_Method::scope_admin . '%'),
1396
-                    ),
1397
-                ),
1398
-            );
1399
-        } else {
1400
-            $query_args = array(array('PMD_scope' => array('LIKE', '%' . EEM_Payment_Method::scope_admin . '%')));
1401
-        }
1402
-        $this->_template_args['payment_methods'] = EEM_Payment_Method::instance()->get_all($query_args);
1403
-    }
1404
-
1405
-
1406
-    /**
1407
-     * txn_attendees_meta_box
1408
-     *    generates HTML for the Attendees Transaction main meta box
1409
-     *
1410
-     * @access public
1411
-     * @param WP_Post $post
1412
-     * @param array   $metabox
1413
-     * @return void
1414
-     * @throws DomainException
1415
-     * @throws EE_Error
1416
-     * @throws InvalidArgumentException
1417
-     * @throws InvalidDataTypeException
1418
-     * @throws InvalidInterfaceException
1419
-     * @throws ReflectionException
1420
-     */
1421
-    public function txn_attendees_meta_box($post, $metabox = array('args' => array()))
1422
-    {
1423
-
1424
-        /** @noinspection NonSecureExtractUsageInspection */
1425
-        extract($metabox['args']);
1426
-        $this->_template_args['post'] = $post;
1427
-        $this->_template_args['event_attendees'] = array();
1428
-        // process items in cart
1429
-        $line_items = $this->_transaction->get_many_related(
1430
-            'Line_Item',
1431
-            array(array('LIN_type' => 'line-item'))
1432
-        );
1433
-        if (! empty($line_items)) {
1434
-            foreach ($line_items as $item) {
1435
-                if ($item instanceof EE_Line_Item) {
1436
-                    switch ($item->OBJ_type()) {
1437
-                        case 'Event':
1438
-                            break;
1439
-                        case 'Ticket':
1440
-                            $ticket = $item->ticket();
1441
-                            // right now we're only handling tickets here.
1442
-                            // Cause its expected that only tickets will have attendees right?
1443
-                            if (! $ticket instanceof EE_Ticket) {
1444
-                                break;
1445
-                            }
1446
-                            try {
1447
-                                $event_name = $ticket->get_event_name();
1448
-                            } catch (Exception $e) {
1449
-                                EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
1450
-                                $event_name = esc_html__('Unknown Event', 'event_espresso');
1451
-                            }
1452
-                            $event_name .= ' - ' . $item->name();
1453
-                            $ticket_price = EEH_Template::format_currency($item->unit_price());
1454
-                            // now get all of the registrations for this transaction that use this ticket
1455
-                            $registrations = $ticket->registrations(
1456
-                                array(array('TXN_ID' => $this->_transaction->ID()))
1457
-                            );
1458
-                            foreach ($registrations as $registration) {
1459
-                                if (! $registration instanceof EE_Registration) {
1460
-                                    break;
1461
-                                }
1462
-                                $this->_template_args['event_attendees'][ $registration->ID() ]['STS_ID']
1463
-                                    = $registration->status_ID();
1464
-                                $this->_template_args['event_attendees'][ $registration->ID() ]['att_num']
1465
-                                    = $registration->count();
1466
-                                $this->_template_args['event_attendees'][ $registration->ID() ]['event_ticket_name']
1467
-                                    = $event_name;
1468
-                                $this->_template_args['event_attendees'][ $registration->ID() ]['ticket_price']
1469
-                                    = $ticket_price;
1470
-                                // attendee info
1471
-                                $attendee = $registration->get_first_related('Attendee');
1472
-                                if ($attendee instanceof EE_Attendee) {
1473
-                                    $this->_template_args['event_attendees'][ $registration->ID() ]['att_id']
1474
-                                        = $attendee->ID();
1475
-                                    $this->_template_args['event_attendees'][ $registration->ID() ]['attendee']
1476
-                                        = $attendee->full_name();
1477
-                                    $this->_template_args['event_attendees'][ $registration->ID() ]['email']
1478
-                                        = '<a href="mailto:' . $attendee->email() . '?subject=' . $event_name
1479
-                                          . esc_html__(
1480
-                                              ' Event',
1481
-                                              'event_espresso'
1482
-                                          )
1483
-                                          . '">' . $attendee->email() . '</a>';
1484
-                                    $this->_template_args['event_attendees'][ $registration->ID() ]['address']
1485
-                                        = EEH_Address::format($attendee, 'inline', false, false);
1486
-                                } else {
1487
-                                    $this->_template_args['event_attendees'][ $registration->ID() ]['att_id'] = '';
1488
-                                    $this->_template_args['event_attendees'][ $registration->ID() ]['attendee'] = '';
1489
-                                    $this->_template_args['event_attendees'][ $registration->ID() ]['email'] = '';
1490
-                                    $this->_template_args['event_attendees'][ $registration->ID() ]['address'] = '';
1491
-                                }
1492
-                            }
1493
-                            break;
1494
-                    }
1495
-                }
1496
-            }
1497
-
1498
-            $this->_template_args['transaction_form_url'] = add_query_arg(
1499
-                array(
1500
-                    'action'  => 'edit_transaction',
1501
-                    'process' => 'attendees',
1502
-                ),
1503
-                TXN_ADMIN_URL
1504
-            );
1505
-            echo EEH_Template::display_template(
1506
-                TXN_TEMPLATE_PATH . 'txn_admin_details_main_meta_box_attendees.template.php',
1507
-                $this->_template_args,
1508
-                true
1509
-            );
1510
-        } else {
1511
-            echo sprintf(
1512
-                esc_html__(
1513
-                    '%1$sFor some reason, there are no attendees registered for this transaction. Likely the registration was abandoned in process.%2$s',
1514
-                    'event_espresso'
1515
-                ),
1516
-                '<p class="important-notice">',
1517
-                '</p>'
1518
-            );
1519
-        }
1520
-    }
1521
-
1522
-
1523
-    /**
1524
-     * txn_registrant_side_meta_box
1525
-     * generates HTML for the Edit Transaction side meta box
1526
-     *
1527
-     * @access public
1528
-     * @return void
1529
-     * @throws DomainException
1530
-     * @throws EE_Error
1531
-     * @throws InvalidArgumentException
1532
-     * @throws InvalidDataTypeException
1533
-     * @throws InvalidInterfaceException
1534
-     * @throws ReflectionException
1535
-     */
1536
-    public function txn_registrant_side_meta_box()
1537
-    {
1538
-        $primary_att = $this->_transaction->primary_registration() instanceof EE_Registration
1539
-            ? $this->_transaction->primary_registration()->get_first_related('Attendee')
1540
-            : null;
1541
-        if (! $primary_att instanceof EE_Attendee) {
1542
-            $this->_template_args['no_attendee_message'] = esc_html__(
1543
-                'There is no attached contact for this transaction.  The transaction either failed due to an error or was abandoned.',
1544
-                'event_espresso'
1545
-            );
1546
-            $primary_att = EEM_Attendee::instance()->create_default_object();
1547
-        }
1548
-        $this->_template_args['ATT_ID'] = $primary_att->ID();
1549
-        $this->_template_args['prime_reg_fname'] = $primary_att->fname();
1550
-        $this->_template_args['prime_reg_lname'] = $primary_att->lname();
1551
-        $this->_template_args['prime_reg_email'] = $primary_att->email();
1552
-        $this->_template_args['prime_reg_phone'] = $primary_att->phone();
1553
-        $this->_template_args['edit_attendee_url'] = EE_Admin_Page::add_query_args_and_nonce(
1554
-            array(
1555
-                'action' => 'edit_attendee',
1556
-                'post'   => $primary_att->ID(),
1557
-            ),
1558
-            REG_ADMIN_URL
1559
-        );
1560
-        // get formatted address for registrant
1561
-        $this->_template_args['formatted_address'] = EEH_Address::format($primary_att);
1562
-        echo EEH_Template::display_template(
1563
-            TXN_TEMPLATE_PATH . 'txn_admin_details_side_meta_box_registrant.template.php',
1564
-            $this->_template_args,
1565
-            true
1566
-        );
1567
-    }
1568
-
1569
-
1570
-    /**
1571
-     * txn_billing_info_side_meta_box
1572
-     *    generates HTML for the Edit Transaction side meta box
1573
-     *
1574
-     * @access public
1575
-     * @return void
1576
-     * @throws DomainException
1577
-     * @throws EE_Error
1578
-     */
1579
-    public function txn_billing_info_side_meta_box()
1580
-    {
1581
-
1582
-        $this->_template_args['billing_form'] = $this->_transaction->billing_info();
1583
-        $this->_template_args['billing_form_url'] = add_query_arg(
1584
-            array('action' => 'edit_transaction', 'process' => 'billing'),
1585
-            TXN_ADMIN_URL
1586
-        );
1587
-
1588
-        $template_path = TXN_TEMPLATE_PATH . 'txn_admin_details_side_meta_box_billing_info.template.php';
1589
-        echo EEH_Template::display_template($template_path, $this->_template_args, true);
1590
-    }
1591
-
1592
-
1593
-    /**
1594
-     * apply_payments_or_refunds
1595
-     *    registers a payment or refund made towards a transaction
1596
-     *
1597
-     * @access public
1598
-     * @return void
1599
-     * @throws EE_Error
1600
-     * @throws InvalidArgumentException
1601
-     * @throws ReflectionException
1602
-     * @throws RuntimeException
1603
-     * @throws InvalidDataTypeException
1604
-     * @throws InvalidInterfaceException
1605
-     */
1606
-    public function apply_payments_or_refunds()
1607
-    {
1608
-        $json_response_data = array('return_data' => false);
1609
-        $valid_data = $this->_validate_payment_request_data();
1610
-        $has_access = EE_Registry::instance()->CAP->current_user_can(
1611
-            'ee_edit_payments',
1612
-            'apply_payment_or_refund_from_registration_details'
1613
-        );
1614
-        if (! empty($valid_data) && $has_access) {
1615
-            $PAY_ID = $valid_data['PAY_ID'];
1616
-            // save  the new payment
1617
-            $payment = $this->_create_payment_from_request_data($valid_data);
1618
-            // get the TXN for this payment
1619
-            $transaction = $payment->transaction();
1620
-            // verify transaction
1621
-            if ($transaction instanceof EE_Transaction) {
1622
-                // calculate_total_payments_and_update_status
1623
-                $this->_process_transaction_payments($transaction);
1624
-                $REG_IDs = $this->_get_REG_IDs_to_apply_payment_to($payment);
1625
-                $this->_remove_existing_registration_payments($payment, $PAY_ID);
1626
-                // apply payment to registrations (if applicable)
1627
-                if (! empty($REG_IDs)) {
1628
-                    $this->_update_registration_payments($transaction, $payment, $REG_IDs);
1629
-                    $this->_maybe_send_notifications();
1630
-                    // now process status changes for the same registrations
1631
-                    $this->_process_registration_status_change($transaction, $REG_IDs);
1632
-                }
1633
-                $this->_maybe_send_notifications($payment);
1634
-                // prepare to render page
1635
-                $json_response_data['return_data'] = $this->_build_payment_json_response($payment, $REG_IDs);
1636
-                do_action(
1637
-                    'AHEE__Transactions_Admin_Page__apply_payments_or_refund__after_recording',
1638
-                    $transaction,
1639
-                    $payment
1640
-                );
1641
-            } else {
1642
-                EE_Error::add_error(
1643
-                    esc_html__(
1644
-                        'A valid Transaction for this payment could not be retrieved.',
1645
-                        'event_espresso'
1646
-                    ),
1647
-                    __FILE__,
1648
-                    __FUNCTION__,
1649
-                    __LINE__
1650
-                );
1651
-            }
1652
-        } elseif ($has_access) {
1653
-            EE_Error::add_error(
1654
-                esc_html__(
1655
-                    'The payment form data could not be processed. Please try again.',
1656
-                    'event_espresso'
1657
-                ),
1658
-                __FILE__,
1659
-                __FUNCTION__,
1660
-                __LINE__
1661
-            );
1662
-        } else {
1663
-            EE_Error::add_error(
1664
-                esc_html__(
1665
-                    'You do not have access to apply payments or refunds to a registration.',
1666
-                    'event_espresso'
1667
-                ),
1668
-                __FILE__,
1669
-                __FUNCTION__,
1670
-                __LINE__
1671
-            );
1672
-        }
1673
-        $notices = EE_Error::get_notices(
1674
-            false,
1675
-            false,
1676
-            false
1677
-        );
1678
-        $this->_template_args = array(
1679
-            'data'    => $json_response_data,
1680
-            'error'   => $notices['errors'],
1681
-            'success' => $notices['success'],
1682
-        );
1683
-        $this->_return_json();
1684
-    }
1685
-
1686
-
1687
-    /**
1688
-     * _validate_payment_request_data
1689
-     *
1690
-     * @return array
1691
-     * @throws EE_Error
1692
-     * @throws InvalidArgumentException
1693
-     * @throws InvalidDataTypeException
1694
-     * @throws InvalidInterfaceException
1695
-     */
1696
-    protected function _validate_payment_request_data()
1697
-    {
1698
-        if (! isset($this->_req_data['txn_admin_payment'])) {
1699
-            return array();
1700
-        }
1701
-        $payment_form = $this->_generate_payment_form_section();
1702
-        try {
1703
-            if ($payment_form->was_submitted()) {
1704
-                $payment_form->receive_form_submission();
1705
-                if (! $payment_form->is_valid()) {
1706
-                    $submission_error_messages = array();
1707
-                    foreach ($payment_form->get_validation_errors_accumulated() as $validation_error) {
1708
-                        if ($validation_error instanceof EE_Validation_Error) {
1709
-                            $submission_error_messages[] = sprintf(
1710
-                                _x('%s : %s', 'Form Section Name : Form Validation Error', 'event_espresso'),
1711
-                                $validation_error->get_form_section()->html_label_text(),
1712
-                                $validation_error->getMessage()
1713
-                            );
1714
-                        }
1715
-                    }
1716
-                    EE_Error::add_error(
1717
-                        implode('<br />', $submission_error_messages),
1718
-                        __FILE__,
1719
-                        __FUNCTION__,
1720
-                        __LINE__
1721
-                    );
1722
-                    return array();
1723
-                }
1724
-            }
1725
-        } catch (EE_Error $e) {
1726
-            EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
1727
-            return array();
1728
-        }
1729
-
1730
-        return $payment_form->valid_data();
1731
-    }
1732
-
1733
-
1734
-    /**
1735
-     * _generate_payment_form_section
1736
-     *
1737
-     * @return EE_Form_Section_Proper
1738
-     * @throws EE_Error
1739
-     */
1740
-    protected function _generate_payment_form_section()
1741
-    {
1742
-        return new EE_Form_Section_Proper(
1743
-            array(
1744
-                'name'        => 'txn_admin_payment',
1745
-                'subsections' => array(
1746
-                    'PAY_ID'          => new EE_Text_Input(
1747
-                        array(
1748
-                            'default'               => 0,
1749
-                            'required'              => false,
1750
-                            'html_label_text'       => esc_html__('Payment ID', 'event_espresso'),
1751
-                            'validation_strategies' => array(new EE_Int_Normalization()),
1752
-                        )
1753
-                    ),
1754
-                    'TXN_ID'          => new EE_Text_Input(
1755
-                        array(
1756
-                            'default'               => 0,
1757
-                            'required'              => true,
1758
-                            'html_label_text'       => esc_html__('Transaction ID', 'event_espresso'),
1759
-                            'validation_strategies' => array(new EE_Int_Normalization()),
1760
-                        )
1761
-                    ),
1762
-                    'type'            => new EE_Text_Input(
1763
-                        array(
1764
-                            'default'               => 1,
1765
-                            'required'              => true,
1766
-                            'html_label_text'       => esc_html__('Payment or Refund', 'event_espresso'),
1767
-                            'validation_strategies' => array(new EE_Int_Normalization()),
1768
-                        )
1769
-                    ),
1770
-                    'amount'          => new EE_Text_Input(
1771
-                        array(
1772
-                            'default'               => 0,
1773
-                            'required'              => true,
1774
-                            'html_label_text'       => esc_html__('Payment amount', 'event_espresso'),
1775
-                            'validation_strategies' => array(new EE_Float_Normalization()),
1776
-                        )
1777
-                    ),
1778
-                    'status'          => new EE_Text_Input(
1779
-                        array(
1780
-                            'default'         => EEM_Payment::status_id_approved,
1781
-                            'required'        => true,
1782
-                            'html_label_text' => esc_html__('Payment status', 'event_espresso'),
1783
-                        )
1784
-                    ),
1785
-                    'PMD_ID'          => new EE_Text_Input(
1786
-                        array(
1787
-                            'default'               => 2,
1788
-                            'required'              => true,
1789
-                            'html_label_text'       => esc_html__('Payment Method', 'event_espresso'),
1790
-                            'validation_strategies' => array(new EE_Int_Normalization()),
1791
-                        )
1792
-                    ),
1793
-                    'date'            => new EE_Text_Input(
1794
-                        array(
1795
-                            'default'         => time(),
1796
-                            'required'        => true,
1797
-                            'html_label_text' => esc_html__('Payment date', 'event_espresso'),
1798
-                        )
1799
-                    ),
1800
-                    'txn_id_chq_nmbr' => new EE_Text_Input(
1801
-                        array(
1802
-                            'default'               => '',
1803
-                            'required'              => false,
1804
-                            'html_label_text'       => esc_html__('Transaction or Cheque Number', 'event_espresso'),
1805
-                            'validation_strategies' => array(
1806
-                                new EE_Max_Length_Validation_Strategy(
1807
-                                    esc_html__('Input too long', 'event_espresso'),
1808
-                                    100
1809
-                                ),
1810
-                            ),
1811
-                        )
1812
-                    ),
1813
-                    'po_number'       => new EE_Text_Input(
1814
-                        array(
1815
-                            'default'               => '',
1816
-                            'required'              => false,
1817
-                            'html_label_text'       => esc_html__('Purchase Order Number', 'event_espresso'),
1818
-                            'validation_strategies' => array(
1819
-                                new EE_Max_Length_Validation_Strategy(
1820
-                                    esc_html__('Input too long', 'event_espresso'),
1821
-                                    100
1822
-                                ),
1823
-                            ),
1824
-                        )
1825
-                    ),
1826
-                    'accounting'      => new EE_Text_Input(
1827
-                        array(
1828
-                            'default'               => '',
1829
-                            'required'              => false,
1830
-                            'html_label_text'       => esc_html__('Extra Field for Accounting', 'event_espresso'),
1831
-                            'validation_strategies' => array(
1832
-                                new EE_Max_Length_Validation_Strategy(
1833
-                                    esc_html__('Input too long', 'event_espresso'),
1834
-                                    100
1835
-                                ),
1836
-                            ),
1837
-                        )
1838
-                    ),
1839
-                ),
1840
-            )
1841
-        );
1842
-    }
1843
-
1844
-
1845
-    /**
1846
-     * _create_payment_from_request_data
1847
-     *
1848
-     * @param array $valid_data
1849
-     * @return EE_Payment
1850
-     * @throws EE_Error
1851
-     * @throws InvalidArgumentException
1852
-     * @throws InvalidDataTypeException
1853
-     * @throws InvalidInterfaceException
1854
-     * @throws ReflectionException
1855
-     */
1856
-    protected function _create_payment_from_request_data($valid_data)
1857
-    {
1858
-        $PAY_ID = $valid_data['PAY_ID'];
1859
-        // get payment amount
1860
-        $amount = $valid_data['amount'] ? abs($valid_data['amount']) : 0;
1861
-        // payments have a type value of 1 and refunds have a type value of -1
1862
-        // so multiplying amount by type will give a positive value for payments, and negative values for refunds
1863
-        $amount = $valid_data['type'] < 0 ? $amount * -1 : $amount;
1864
-        // for some reason the date string coming in has extra spaces between the date and time.  This fixes that.
1865
-        $date = $valid_data['date']
1866
-            ? preg_replace('/\s+/', ' ', $valid_data['date'])
1867
-            : date('Y-m-d g:i a', current_time('timestamp'));
1868
-        $payment = EE_Payment::new_instance(
1869
-            array(
1870
-                'TXN_ID'              => $valid_data['TXN_ID'],
1871
-                'STS_ID'              => $valid_data['status'],
1872
-                'PAY_timestamp'       => $date,
1873
-                'PAY_source'          => EEM_Payment_Method::scope_admin,
1874
-                'PMD_ID'              => $valid_data['PMD_ID'],
1875
-                'PAY_amount'          => $amount,
1876
-                'PAY_txn_id_chq_nmbr' => $valid_data['txn_id_chq_nmbr'],
1877
-                'PAY_po_number'       => $valid_data['po_number'],
1878
-                'PAY_extra_accntng'   => $valid_data['accounting'],
1879
-                'PAY_details'         => $valid_data,
1880
-                'PAY_ID'              => $PAY_ID,
1881
-            ),
1882
-            '',
1883
-            array('Y-m-d', 'g:i a')
1884
-        );
1885
-
1886
-        if (! $payment->save()) {
1887
-            EE_Error::add_error(
1888
-                sprintf(
1889
-                    esc_html__('Payment %1$d has not been successfully saved to the database.', 'event_espresso'),
1890
-                    $payment->ID()
1891
-                ),
1892
-                __FILE__,
1893
-                __FUNCTION__,
1894
-                __LINE__
1895
-            );
1896
-        }
1897
-
1898
-        return $payment;
1899
-    }
1900
-
1901
-
1902
-    /**
1903
-     * _process_transaction_payments
1904
-     *
1905
-     * @param \EE_Transaction $transaction
1906
-     * @return void
1907
-     * @throws EE_Error
1908
-     * @throws InvalidArgumentException
1909
-     * @throws ReflectionException
1910
-     * @throws InvalidDataTypeException
1911
-     * @throws InvalidInterfaceException
1912
-     */
1913
-    protected function _process_transaction_payments(EE_Transaction $transaction)
1914
-    {
1915
-        /** @type EE_Transaction_Payments $transaction_payments */
1916
-        $transaction_payments = EE_Registry::instance()->load_class('Transaction_Payments');
1917
-        // update the transaction with this payment
1918
-        if ($transaction_payments->calculate_total_payments_and_update_status($transaction)) {
1919
-            EE_Error::add_success(
1920
-                esc_html__(
1921
-                    'The payment has been processed successfully.',
1922
-                    'event_espresso'
1923
-                ),
1924
-                __FILE__,
1925
-                __FUNCTION__,
1926
-                __LINE__
1927
-            );
1928
-        } else {
1929
-            EE_Error::add_error(
1930
-                esc_html__(
1931
-                    'The payment was processed successfully but the amount paid for the transaction was not updated.',
1932
-                    'event_espresso'
1933
-                ),
1934
-                __FILE__,
1935
-                __FUNCTION__,
1936
-                __LINE__
1937
-            );
1938
-        }
1939
-    }
1940
-
1941
-
1942
-    /**
1943
-     * _get_REG_IDs_to_apply_payment_to
1944
-     * returns a list of registration IDs that the payment will apply to
1945
-     *
1946
-     * @param \EE_Payment $payment
1947
-     * @return array
1948
-     * @throws EE_Error
1949
-     * @throws InvalidArgumentException
1950
-     * @throws InvalidDataTypeException
1951
-     * @throws InvalidInterfaceException
1952
-     * @throws ReflectionException
1953
-     */
1954
-    protected function _get_REG_IDs_to_apply_payment_to(EE_Payment $payment)
1955
-    {
1956
-        $REG_IDs = array();
1957
-        // grab array of IDs for specific registrations to apply changes to
1958
-        if (isset($this->_req_data['txn_admin_payment']['registrations'])) {
1959
-            $REG_IDs = (array) $this->_req_data['txn_admin_payment']['registrations'];
1960
-        }
1961
-        // nothing specified ? then get all reg IDs
1962
-        if (empty($REG_IDs)) {
1963
-            $registrations = $payment->transaction()->registrations();
1964
-            $REG_IDs = ! empty($registrations)
1965
-                ? array_keys($registrations)
1966
-                : $this->_get_existing_reg_payment_REG_IDs($payment);
1967
-        }
1968
-
1969
-        // ensure that REG_IDs are integers and NOT strings
1970
-        return array_map('intval', $REG_IDs);
1971
-    }
1972
-
1973
-
1974
-    /**
1975
-     * @return array
1976
-     */
1977
-    public function existing_reg_payment_REG_IDs()
1978
-    {
1979
-        return $this->_existing_reg_payment_REG_IDs;
1980
-    }
1981
-
1982
-
1983
-    /**
1984
-     * @param array $existing_reg_payment_REG_IDs
1985
-     */
1986
-    public function set_existing_reg_payment_REG_IDs($existing_reg_payment_REG_IDs = null)
1987
-    {
1988
-        $this->_existing_reg_payment_REG_IDs = $existing_reg_payment_REG_IDs;
1989
-    }
1990
-
1991
-
1992
-    /**
1993
-     * _get_existing_reg_payment_REG_IDs
1994
-     * returns a list of registration IDs that the payment is currently related to
1995
-     * as recorded in the database
1996
-     *
1997
-     * @param \EE_Payment $payment
1998
-     * @return array
1999
-     * @throws EE_Error
2000
-     * @throws InvalidArgumentException
2001
-     * @throws InvalidDataTypeException
2002
-     * @throws InvalidInterfaceException
2003
-     * @throws ReflectionException
2004
-     */
2005
-    protected function _get_existing_reg_payment_REG_IDs(EE_Payment $payment)
2006
-    {
2007
-        if ($this->existing_reg_payment_REG_IDs() === null) {
2008
-            // let's get any existing reg payment records for this payment
2009
-            $existing_reg_payment_REG_IDs = $payment->get_many_related('Registration');
2010
-            // but we only want the REG IDs, so grab the array keys
2011
-            $existing_reg_payment_REG_IDs = ! empty($existing_reg_payment_REG_IDs)
2012
-                ? array_keys($existing_reg_payment_REG_IDs)
2013
-                : array();
2014
-            $this->set_existing_reg_payment_REG_IDs($existing_reg_payment_REG_IDs);
2015
-        }
2016
-
2017
-        return $this->existing_reg_payment_REG_IDs();
2018
-    }
2019
-
2020
-
2021
-    /**
2022
-     * _remove_existing_registration_payments
2023
-     * this calculates the difference between existing relations
2024
-     * to the supplied payment and the new list registration IDs,
2025
-     * removes any related registrations that no longer apply,
2026
-     * and then updates the registration paid fields
2027
-     *
2028
-     * @param \EE_Payment $payment
2029
-     * @param int         $PAY_ID
2030
-     * @return bool;
2031
-     * @throws EE_Error
2032
-     * @throws InvalidArgumentException
2033
-     * @throws ReflectionException
2034
-     * @throws InvalidDataTypeException
2035
-     * @throws InvalidInterfaceException
2036
-     */
2037
-    protected function _remove_existing_registration_payments(EE_Payment $payment, $PAY_ID = 0)
2038
-    {
2039
-        // newly created payments will have nothing recorded for $PAY_ID
2040
-        if (absint($PAY_ID) === 0) {
2041
-            return false;
2042
-        }
2043
-        $existing_reg_payment_REG_IDs = $this->_get_existing_reg_payment_REG_IDs($payment);
2044
-        if (empty($existing_reg_payment_REG_IDs)) {
2045
-            return false;
2046
-        }
2047
-        /** @type EE_Transaction_Payments $transaction_payments */
2048
-        $transaction_payments = EE_Registry::instance()->load_class('Transaction_Payments');
2049
-
2050
-        return $transaction_payments->delete_registration_payments_and_update_registrations(
2051
-            $payment,
2052
-            array(
2053
-                array(
2054
-                    'PAY_ID' => $payment->ID(),
2055
-                    'REG_ID' => array('IN', $existing_reg_payment_REG_IDs),
2056
-                ),
2057
-            )
2058
-        );
2059
-    }
2060
-
2061
-
2062
-    /**
2063
-     * _update_registration_payments
2064
-     * this applies the payments to the selected registrations
2065
-     * but only if they have not already been paid for
2066
-     *
2067
-     * @param  EE_Transaction $transaction
2068
-     * @param \EE_Payment     $payment
2069
-     * @param array           $REG_IDs
2070
-     * @return void
2071
-     * @throws EE_Error
2072
-     * @throws InvalidArgumentException
2073
-     * @throws ReflectionException
2074
-     * @throws RuntimeException
2075
-     * @throws InvalidDataTypeException
2076
-     * @throws InvalidInterfaceException
2077
-     */
2078
-    protected function _update_registration_payments(
2079
-        EE_Transaction $transaction,
2080
-        EE_Payment $payment,
2081
-        $REG_IDs = array()
2082
-    ) {
2083
-        // we can pass our own custom set of registrations to EE_Payment_Processor::process_registration_payments()
2084
-        // so let's do that using our set of REG_IDs from the form
2085
-        $registration_query_where_params = array(
2086
-            'REG_ID' => array('IN', $REG_IDs),
2087
-        );
2088
-        // but add in some conditions regarding payment,
2089
-        // so that we don't apply payments to registrations that are free or have already been paid for
2090
-        // but ONLY if the payment is NOT a refund ( ie: the payment amount is not negative )
2091
-        if (! $payment->is_a_refund()) {
2092
-            $registration_query_where_params['REG_final_price'] = array('!=', 0);
2093
-            $registration_query_where_params['REG_final_price*'] = array('!=', 'REG_paid', true);
2094
-        }
2095
-        $registrations = $transaction->registrations(array($registration_query_where_params));
2096
-        if (! empty($registrations)) {
2097
-            /** @type EE_Payment_Processor $payment_processor */
2098
-            $payment_processor = EE_Registry::instance()->load_core('Payment_Processor');
2099
-            $payment_processor->process_registration_payments($transaction, $payment, $registrations);
2100
-        }
2101
-    }
2102
-
2103
-
2104
-    /**
2105
-     * _process_registration_status_change
2106
-     * This processes requested registration status changes for all the registrations
2107
-     * on a given transaction and (optionally) sends out notifications for the changes.
2108
-     *
2109
-     * @param  EE_Transaction $transaction
2110
-     * @param array           $REG_IDs
2111
-     * @return bool
2112
-     * @throws EE_Error
2113
-     * @throws InvalidArgumentException
2114
-     * @throws ReflectionException
2115
-     * @throws InvalidDataTypeException
2116
-     * @throws InvalidInterfaceException
2117
-     */
2118
-    protected function _process_registration_status_change(EE_Transaction $transaction, $REG_IDs = array())
2119
-    {
2120
-        // first if there is no change in status then we get out.
2121
-        if (! isset($this->_req_data['txn_reg_status_change']['reg_status'])
2122
-            || $this->_req_data['txn_reg_status_change']['reg_status'] === 'NAN'
2123
-        ) {
2124
-            // no error message, no change requested, just nothing to do man.
2125
-            return false;
2126
-        }
2127
-        /** @type EE_Transaction_Processor $transaction_processor */
2128
-        $transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
2129
-
2130
-        // made it here dude?  Oh WOW.  K, let's take care of changing the statuses
2131
-        return $transaction_processor->manually_update_registration_statuses(
2132
-            $transaction,
2133
-            sanitize_text_field($this->_req_data['txn_reg_status_change']['reg_status']),
2134
-            array(array('REG_ID' => array('IN', $REG_IDs)))
2135
-        );
2136
-    }
2137
-
2138
-
2139
-    /**
2140
-     * _build_payment_json_response
2141
-     *
2142
-     * @access public
2143
-     * @param \EE_Payment $payment
2144
-     * @param array       $REG_IDs
2145
-     * @param bool | null $delete_txn_reg_status_change
2146
-     * @return array
2147
-     * @throws EE_Error
2148
-     * @throws InvalidArgumentException
2149
-     * @throws InvalidDataTypeException
2150
-     * @throws InvalidInterfaceException
2151
-     * @throws ReflectionException
2152
-     */
2153
-    protected function _build_payment_json_response(
2154
-        EE_Payment $payment,
2155
-        $REG_IDs = array(),
2156
-        $delete_txn_reg_status_change = null
2157
-    ) {
2158
-        // was the payment deleted ?
2159
-        if (is_bool($delete_txn_reg_status_change)) {
2160
-            return array(
2161
-                'PAY_ID'                       => $payment->ID(),
2162
-                'amount'                       => $payment->amount(),
2163
-                'total_paid'                   => $payment->transaction()->paid(),
2164
-                'txn_status'                   => $payment->transaction()->status_ID(),
2165
-                'pay_status'                   => $payment->STS_ID(),
2166
-                'registrations'                => $this->_registration_payment_data_array($REG_IDs),
2167
-                'delete_txn_reg_status_change' => $delete_txn_reg_status_change,
2168
-            );
2169
-        } else {
2170
-            $this->_get_payment_status_array();
2171
-
2172
-            return array(
2173
-                'amount'           => $payment->amount(),
2174
-                'total_paid'       => $payment->transaction()->paid(),
2175
-                'txn_status'       => $payment->transaction()->status_ID(),
2176
-                'pay_status'       => $payment->STS_ID(),
2177
-                'PAY_ID'           => $payment->ID(),
2178
-                'STS_ID'           => $payment->STS_ID(),
2179
-                'status'           => self::$_pay_status[ $payment->STS_ID() ],
2180
-                'date'             => $payment->timestamp('Y-m-d', 'h:i a'),
2181
-                'method'           => strtoupper($payment->source()),
2182
-                'PM_ID'            => $payment->payment_method() ? $payment->payment_method()->ID() : 1,
2183
-                'gateway'          => $payment->payment_method()
2184
-                    ? $payment->payment_method()->admin_name()
2185
-                    : esc_html__('Unknown', 'event_espresso'),
2186
-                'gateway_response' => $payment->gateway_response(),
2187
-                'txn_id_chq_nmbr'  => $payment->txn_id_chq_nmbr(),
2188
-                'po_number'        => $payment->po_number(),
2189
-                'extra_accntng'    => $payment->extra_accntng(),
2190
-                'registrations'    => $this->_registration_payment_data_array($REG_IDs),
2191
-            );
2192
-        }
2193
-    }
2194
-
2195
-
2196
-    /**
2197
-     * delete_payment
2198
-     *    delete a payment or refund made towards a transaction
2199
-     *
2200
-     * @access public
2201
-     * @return void
2202
-     * @throws EE_Error
2203
-     * @throws InvalidArgumentException
2204
-     * @throws ReflectionException
2205
-     * @throws InvalidDataTypeException
2206
-     * @throws InvalidInterfaceException
2207
-     */
2208
-    public function delete_payment()
2209
-    {
2210
-        $json_response_data = array('return_data' => false);
2211
-        $PAY_ID = isset($this->_req_data['delete_txn_admin_payment']['PAY_ID'])
2212
-            ? absint($this->_req_data['delete_txn_admin_payment']['PAY_ID'])
2213
-            : 0;
2214
-        $can_delete = EE_Registry::instance()->CAP->current_user_can(
2215
-            'ee_delete_payments',
2216
-            'delete_payment_from_registration_details'
2217
-        );
2218
-        if ($PAY_ID && $can_delete) {
2219
-            $delete_txn_reg_status_change = isset($this->_req_data['delete_txn_reg_status_change'])
2220
-                ? $this->_req_data['delete_txn_reg_status_change']
2221
-                : false;
2222
-            $payment = EEM_Payment::instance()->get_one_by_ID($PAY_ID);
2223
-            if ($payment instanceof EE_Payment) {
2224
-                $REG_IDs = $this->_get_existing_reg_payment_REG_IDs($payment);
2225
-                /** @type EE_Transaction_Payments $transaction_payments */
2226
-                $transaction_payments = EE_Registry::instance()->load_class('Transaction_Payments');
2227
-                if ($transaction_payments->delete_payment_and_update_transaction($payment)) {
2228
-                    $json_response_data['return_data'] = $this->_build_payment_json_response(
2229
-                        $payment,
2230
-                        $REG_IDs,
2231
-                        $delete_txn_reg_status_change
2232
-                    );
2233
-                    if ($delete_txn_reg_status_change) {
2234
-                        $this->_req_data['txn_reg_status_change'] = $delete_txn_reg_status_change;
2235
-                        // MAKE sure we also add the delete_txn_req_status_change to the
2236
-                        // $_REQUEST global because that's how messages will be looking for it.
2237
-                        $_REQUEST['txn_reg_status_change'] = $delete_txn_reg_status_change;
2238
-                        $this->_maybe_send_notifications();
2239
-                        $this->_process_registration_status_change($payment->transaction(), $REG_IDs);
2240
-                    }
2241
-                }
2242
-            } else {
2243
-                EE_Error::add_error(
2244
-                    esc_html__('Valid Payment data could not be retrieved from the database.', 'event_espresso'),
2245
-                    __FILE__,
2246
-                    __FUNCTION__,
2247
-                    __LINE__
2248
-                );
2249
-            }
2250
-        } elseif ($can_delete) {
2251
-            EE_Error::add_error(
2252
-                esc_html__(
2253
-                    'A valid Payment ID was not received, therefore payment form data could not be loaded.',
2254
-                    'event_espresso'
2255
-                ),
2256
-                __FILE__,
2257
-                __FUNCTION__,
2258
-                __LINE__
2259
-            );
2260
-        } else {
2261
-            EE_Error::add_error(
2262
-                esc_html__(
2263
-                    'You do not have access to delete a payment.',
2264
-                    'event_espresso'
2265
-                ),
2266
-                __FILE__,
2267
-                __FUNCTION__,
2268
-                __LINE__
2269
-            );
2270
-        }
2271
-        $notices = EE_Error::get_notices(false, false, false);
2272
-        $this->_template_args = array(
2273
-            'data'      => $json_response_data,
2274
-            'success'   => $notices['success'],
2275
-            'error'     => $notices['errors'],
2276
-            'attention' => $notices['attention'],
2277
-        );
2278
-        $this->_return_json();
2279
-    }
2280
-
2281
-
2282
-    /**
2283
-     * _registration_payment_data_array
2284
-     * adds info for 'owing' and 'paid' for each registration to the json response
2285
-     *
2286
-     * @access protected
2287
-     * @param array $REG_IDs
2288
-     * @return array
2289
-     * @throws EE_Error
2290
-     * @throws InvalidArgumentException
2291
-     * @throws InvalidDataTypeException
2292
-     * @throws InvalidInterfaceException
2293
-     * @throws ReflectionException
2294
-     */
2295
-    protected function _registration_payment_data_array($REG_IDs)
2296
-    {
2297
-        $registration_payment_data = array();
2298
-        // if non empty reg_ids lets get an array of registrations and update the values for the apply_payment/refund rows.
2299
-        if (! empty($REG_IDs)) {
2300
-            $registrations = EEM_Registration::instance()->get_all(array(array('REG_ID' => array('IN', $REG_IDs))));
2301
-            foreach ($registrations as $registration) {
2302
-                if ($registration instanceof EE_Registration) {
2303
-                    $registration_payment_data[ $registration->ID() ] = array(
2304
-                        'paid'  => $registration->pretty_paid(),
2305
-                        'owing' => EEH_Template::format_currency($registration->final_price() - $registration->paid()),
2306
-                    );
2307
-                }
2308
-            }
2309
-        }
2310
-
2311
-        return $registration_payment_data;
2312
-    }
2313
-
2314
-
2315
-    /**
2316
-     * _maybe_send_notifications
2317
-     * determines whether or not the admin has indicated that notifications should be sent.
2318
-     * If so, will toggle a filter switch for delivering registration notices.
2319
-     * If passed an EE_Payment object, then it will trigger payment notifications instead.
2320
-     *
2321
-     * @access protected
2322
-     * @param \EE_Payment | null $payment
2323
-     */
2324
-    protected function _maybe_send_notifications($payment = null)
2325
-    {
2326
-        switch ($payment instanceof EE_Payment) {
2327
-            // payment notifications
2328
-            case true:
2329
-                if (isset($this->_req_data['txn_payments']['send_notifications'])
2330
-                    && filter_var(
2331
-                        $this->_req_data['txn_payments']['send_notifications'],
2332
-                        FILTER_VALIDATE_BOOLEAN
2333
-                    )
2334
-                ) {
2335
-                    $this->_process_payment_notification($payment);
2336
-                }
2337
-                break;
2338
-            // registration notifications
2339
-            case false:
2340
-                if (isset($this->_req_data['txn_reg_status_change']['send_notifications'])
2341
-                    && filter_var(
2342
-                        $this->_req_data['txn_reg_status_change']['send_notifications'],
2343
-                        FILTER_VALIDATE_BOOLEAN
2344
-                    )
2345
-                ) {
2346
-                    add_filter('FHEE__EED_Messages___maybe_registration__deliver_notifications', '__return_true');
2347
-                }
2348
-                break;
2349
-        }
2350
-    }
2351
-
2352
-
2353
-    /**
2354
-     * _send_payment_reminder
2355
-     *    generates HTML for the View Transaction Details Admin page
2356
-     *
2357
-     * @access protected
2358
-     * @return void
2359
-     * @throws EE_Error
2360
-     * @throws InvalidArgumentException
2361
-     * @throws InvalidDataTypeException
2362
-     * @throws InvalidInterfaceException
2363
-     */
2364
-    protected function _send_payment_reminder()
2365
-    {
2366
-        $TXN_ID = ! empty($this->_req_data['TXN_ID']) ? absint($this->_req_data['TXN_ID']) : false;
2367
-        $transaction = EEM_Transaction::instance()->get_one_by_ID($TXN_ID);
2368
-        $query_args = isset($this->_req_data['redirect_to']) ? array(
2369
-            'action' => $this->_req_data['redirect_to'],
2370
-            'TXN_ID' => $this->_req_data['TXN_ID'],
2371
-        ) : array();
2372
-        do_action(
2373
-            'AHEE__Transactions_Admin_Page___send_payment_reminder__process_admin_payment_reminder',
2374
-            $transaction
2375
-        );
2376
-        $this->_redirect_after_action(
2377
-            false,
2378
-            esc_html__('payment reminder', 'event_espresso'),
2379
-            esc_html__('sent', 'event_espresso'),
2380
-            $query_args,
2381
-            true
2382
-        );
2383
-    }
2384
-
2385
-
2386
-    /**
2387
-     *  get_transactions
2388
-     *    get transactions for given parameters (used by list table)
2389
-     *
2390
-     * @param  int     $perpage how many transactions displayed per page
2391
-     * @param  boolean $count   return the count or objects
2392
-     * @param string   $view
2393
-     * @return mixed int = count || array of transaction objects
2394
-     * @throws EE_Error
2395
-     * @throws InvalidArgumentException
2396
-     * @throws InvalidDataTypeException
2397
-     * @throws InvalidInterfaceException
2398
-     */
2399
-    public function get_transactions($perpage, $count = false, $view = '')
2400
-    {
2401
-
2402
-        $TXN = EEM_Transaction::instance();
2403
-
2404
-        $start_date = isset($this->_req_data['txn-filter-start-date'])
2405
-            ? wp_strip_all_tags($this->_req_data['txn-filter-start-date'])
2406
-            : date(
2407
-                'm/d/Y',
2408
-                strtotime('-10 year')
2409
-            );
2410
-        $end_date = isset($this->_req_data['txn-filter-end-date'])
2411
-            ? wp_strip_all_tags($this->_req_data['txn-filter-end-date'])
2412
-            : date('m/d/Y');
2413
-
2414
-        // make sure our timestamps start and end right at the boundaries for each day
2415
-        $start_date = date('Y-m-d', strtotime($start_date)) . ' 00:00:00';
2416
-        $end_date = date('Y-m-d', strtotime($end_date)) . ' 23:59:59';
2417
-
2418
-
2419
-        // convert to timestamps
2420
-        $start_date = strtotime($start_date);
2421
-        $end_date = strtotime($end_date);
2422
-
2423
-        // makes sure start date is the lowest value and vice versa
2424
-        $start_date = min($start_date, $end_date);
2425
-        $end_date = max($start_date, $end_date);
2426
-
2427
-        // convert to correct format for query
2428
-        $start_date = EEM_Transaction::instance()->convert_datetime_for_query(
2429
-            'TXN_timestamp',
2430
-            date('Y-m-d H:i:s', $start_date),
2431
-            'Y-m-d H:i:s'
2432
-        );
2433
-        $end_date = EEM_Transaction::instance()->convert_datetime_for_query(
2434
-            'TXN_timestamp',
2435
-            date('Y-m-d H:i:s', $end_date),
2436
-            'Y-m-d H:i:s'
2437
-        );
2438
-
2439
-
2440
-        // set orderby
2441
-        $this->_req_data['orderby'] = ! empty($this->_req_data['orderby']) ? $this->_req_data['orderby'] : '';
2442
-
2443
-        switch ($this->_req_data['orderby']) {
2444
-            case 'TXN_ID':
2445
-                $orderby = 'TXN_ID';
2446
-                break;
2447
-            case 'ATT_fname':
2448
-                $orderby = 'Registration.Attendee.ATT_fname';
2449
-                break;
2450
-            case 'event_name':
2451
-                $orderby = 'Registration.Event.EVT_name';
2452
-                break;
2453
-            default: // 'TXN_timestamp'
2454
-                $orderby = 'TXN_timestamp';
2455
-        }
2456
-
2457
-        $sort = ! empty($this->_req_data['order']) ? $this->_req_data['order'] : 'DESC';
2458
-        $current_page = ! empty($this->_req_data['paged']) ? $this->_req_data['paged'] : 1;
2459
-        $per_page = ! empty($perpage) ? $perpage : 10;
2460
-        $per_page = ! empty($this->_req_data['perpage']) ? $this->_req_data['perpage'] : $per_page;
2461
-
2462
-        $offset = ($current_page - 1) * $per_page;
2463
-        $limit = array($offset, $per_page);
2464
-
2465
-        $_where = array(
2466
-            'TXN_timestamp'          => array('BETWEEN', array($start_date, $end_date)),
2467
-            'Registration.REG_count' => 1,
2468
-        );
2469
-
2470
-        if (isset($this->_req_data['EVT_ID'])) {
2471
-            $_where['Registration.EVT_ID'] = $this->_req_data['EVT_ID'];
2472
-        }
2473
-
2474
-        if (isset($this->_req_data['s'])) {
2475
-            $search_string = '%' . $this->_req_data['s'] . '%';
2476
-            $_where['OR'] = array(
2477
-                'Registration.Event.EVT_name'         => array('LIKE', $search_string),
2478
-                'Registration.Event.EVT_desc'         => array('LIKE', $search_string),
2479
-                'Registration.Event.EVT_short_desc'   => array('LIKE', $search_string),
2480
-                'Registration.Attendee.ATT_full_name' => array('LIKE', $search_string),
2481
-                'Registration.Attendee.ATT_fname'     => array('LIKE', $search_string),
2482
-                'Registration.Attendee.ATT_lname'     => array('LIKE', $search_string),
2483
-                'Registration.Attendee.ATT_short_bio' => array('LIKE', $search_string),
2484
-                'Registration.Attendee.ATT_email'     => array('LIKE', $search_string),
2485
-                'Registration.Attendee.ATT_address'   => array('LIKE', $search_string),
2486
-                'Registration.Attendee.ATT_address2'  => array('LIKE', $search_string),
2487
-                'Registration.Attendee.ATT_city'      => array('LIKE', $search_string),
2488
-                'Registration.REG_final_price'        => array('LIKE', $search_string),
2489
-                'Registration.REG_code'               => array('LIKE', $search_string),
2490
-                'Registration.REG_count'              => array('LIKE', $search_string),
2491
-                'Registration.REG_group_size'         => array('LIKE', $search_string),
2492
-                'Registration.Ticket.TKT_name'        => array('LIKE', $search_string),
2493
-                'Registration.Ticket.TKT_description' => array('LIKE', $search_string),
2494
-                'Payment.PAY_source'                  => array('LIKE', $search_string),
2495
-                'Payment.Payment_Method.PMD_name'     => array('LIKE', $search_string),
2496
-                'TXN_session_data'                    => array('LIKE', $search_string),
2497
-                'Payment.PAY_txn_id_chq_nmbr'         => array('LIKE', $search_string),
2498
-            );
2499
-        }
2500
-
2501
-        // failed transactions
2502
-        $failed = (! empty($this->_req_data['status']) && $this->_req_data['status'] === 'failed' && ! $count)
2503
-                  || ($count && $view === 'failed');
2504
-        $abandoned = (! empty($this->_req_data['status']) && $this->_req_data['status'] === 'abandoned' && ! $count)
2505
-                     || ($count && $view === 'abandoned');
2506
-        $incomplete = (! empty($this->_req_data['status']) && $this->_req_data['status'] === 'incomplete' && ! $count)
2507
-                      || ($count && $view === 'incomplete');
2508
-
2509
-        if ($failed) {
2510
-            $_where['STS_ID'] = EEM_Transaction::failed_status_code;
2511
-        } elseif ($abandoned) {
2512
-            $_where['STS_ID'] = EEM_Transaction::abandoned_status_code;
2513
-        } elseif ($incomplete) {
2514
-            $_where['STS_ID'] = EEM_Transaction::incomplete_status_code;
2515
-        } else {
2516
-            $_where['STS_ID'] = array('!=', EEM_Transaction::failed_status_code);
2517
-            $_where['STS_ID*'] = array('!=', EEM_Transaction::abandoned_status_code);
2518
-        }
2519
-
2520
-        $query_params = apply_filters(
2521
-            'FHEE__Transactions_Admin_Page___get_transactions_query_params',
2522
-            array(
2523
-                $_where,
2524
-                'order_by'                 => array($orderby => $sort),
2525
-                'limit'                    => $limit,
2526
-                'default_where_conditions' => EEM_Base::default_where_conditions_this_only,
2527
-            ),
2528
-            $this->_req_data,
2529
-            $view,
2530
-            $count
2531
-        );
2532
-
2533
-        $transactions = $count
2534
-            ? $TXN->count(array($query_params[0]), 'TXN_ID', true)
2535
-            : $TXN->get_all($query_params);
2536
-
2537
-        return $transactions;
2538
-    }
2539
-
2540
-
2541
-    /**
2542
-     * @since $VID:$
2543
-     * @throws EE_Error
2544
-     * @throws InvalidArgumentException
2545
-     * @throws InvalidDataTypeException
2546
-     * @throws InvalidInterfaceException
2547
-     * @throws ReflectionException
2548
-     * @throws RuntimeException
2549
-     */
2550
-    public function recalculateLineItems()
2551
-    {
2552
-        $TXN_ID = ! empty($this->_req_data['TXN_ID']) ? absint($this->_req_data['TXN_ID']) : false;
2553
-        /** @var EE_Transaction $transaction */
2554
-        $transaction = EEM_Transaction::instance()->get_one_by_ID($TXN_ID);
2555
-        $total_line_item = $transaction->total_line_item(false);
2556
-        $success = false;
2557
-        if ($total_line_item instanceof EE_Line_Item) {
2558
-            EEH_Line_Item::resetIsTaxableForTickets($total_line_item);
2559
-            $success = EEH_Line_Item::apply_taxes($total_line_item);
2560
-        }
2561
-        if ($success
2562
-            && EEH_Money::compare_floats($transaction->total(), $total_line_item->total(), '!=')
2563
-        ) {
2564
-            $transaction->set_total($total_line_item->total());
2565
-            $success = $transaction->update_status_based_on_total_paid();
2566
-        }
2567
-        $this->_redirect_after_action(
2568
-            (bool) $success,
2569
-            esc_html__('Transaction taxes and totals', 'event_espresso'),
2570
-            esc_html__('recalculated', 'event_espresso'),
2571
-            isset($this->_req_data['redirect_to'])
2572
-                ? array(
2573
-                'action' => $this->_req_data['redirect_to'],
2574
-                'TXN_ID' => $this->_req_data['TXN_ID'],
2575
-            )
2576
-                : array(),
2577
-            true
2578
-        );
2579
-    }
16
+	/**
17
+	 * @var EE_Transaction
18
+	 */
19
+	private $_transaction;
20
+
21
+	/**
22
+	 * @var EE_Session
23
+	 */
24
+	private $_session;
25
+
26
+	/**
27
+	 * @var array $_txn_status
28
+	 */
29
+	private static $_txn_status;
30
+
31
+	/**
32
+	 * @var array $_pay_status
33
+	 */
34
+	private static $_pay_status;
35
+
36
+	/**
37
+	 * @var array $_existing_reg_payment_REG_IDs
38
+	 */
39
+	protected $_existing_reg_payment_REG_IDs;
40
+
41
+
42
+	/**
43
+	 *    _init_page_props
44
+	 *
45
+	 * @return void
46
+	 */
47
+	protected function _init_page_props()
48
+	{
49
+		$this->page_slug = TXN_PG_SLUG;
50
+		$this->page_label = esc_html__('Transactions', 'event_espresso');
51
+		$this->_admin_base_url = TXN_ADMIN_URL;
52
+		$this->_admin_base_path = TXN_ADMIN;
53
+	}
54
+
55
+
56
+	/**
57
+	 *    _ajax_hooks
58
+	 *
59
+	 * @return void
60
+	 */
61
+	protected function _ajax_hooks()
62
+	{
63
+		add_action('wp_ajax_espresso_apply_payment', array($this, 'apply_payments_or_refunds'));
64
+		add_action('wp_ajax_espresso_apply_refund', array($this, 'apply_payments_or_refunds'));
65
+		add_action('wp_ajax_espresso_delete_payment', array($this, 'delete_payment'));
66
+	}
67
+
68
+
69
+	/**
70
+	 *    _define_page_props
71
+	 *
72
+	 * @return void
73
+	 */
74
+	protected function _define_page_props()
75
+	{
76
+		$this->_admin_page_title = $this->page_label;
77
+		$this->_labels = array(
78
+			'buttons' => array(
79
+				'add'    => esc_html__('Add New Transaction', 'event_espresso'),
80
+				'edit'   => esc_html__('Edit Transaction', 'event_espresso'),
81
+				'delete' => esc_html__('Delete Transaction', 'event_espresso'),
82
+			),
83
+		);
84
+	}
85
+
86
+
87
+	/**
88
+	 *        grab url requests and route them
89
+	 *
90
+	 * @access private
91
+	 * @return void
92
+	 * @throws EE_Error
93
+	 * @throws InvalidArgumentException
94
+	 * @throws InvalidDataTypeException
95
+	 * @throws InvalidInterfaceException
96
+	 */
97
+	public function _set_page_routes()
98
+	{
99
+
100
+		$this->_set_transaction_status_array();
101
+
102
+		$txn_id = ! empty($this->_req_data['TXN_ID'])
103
+				  && ! is_array($this->_req_data['TXN_ID'])
104
+			? $this->_req_data['TXN_ID']
105
+			: 0;
106
+
107
+		$this->_page_routes = array(
108
+
109
+			'default' => array(
110
+				'func'       => '_transactions_overview_list_table',
111
+				'capability' => 'ee_read_transactions',
112
+			),
113
+
114
+			'view_transaction' => array(
115
+				'func'       => '_transaction_details',
116
+				'capability' => 'ee_read_transaction',
117
+				'obj_id'     => $txn_id,
118
+			),
119
+
120
+			'send_payment_reminder' => array(
121
+				'func'       => '_send_payment_reminder',
122
+				'noheader'   => true,
123
+				'capability' => 'ee_send_message',
124
+			),
125
+
126
+			'espresso_apply_payment' => array(
127
+				'func'       => 'apply_payments_or_refunds',
128
+				'noheader'   => true,
129
+				'capability' => 'ee_edit_payments',
130
+			),
131
+
132
+			'espresso_apply_refund' => array(
133
+				'func'       => 'apply_payments_or_refunds',
134
+				'noheader'   => true,
135
+				'capability' => 'ee_edit_payments',
136
+			),
137
+
138
+			'espresso_delete_payment' => array(
139
+				'func'       => 'delete_payment',
140
+				'noheader'   => true,
141
+				'capability' => 'ee_delete_payments',
142
+			),
143
+
144
+			'espresso_recalculate_line_items' => array(
145
+				'func'       => 'recalculateLineItems',
146
+				'noheader'   => true,
147
+				'capability' => 'ee_edit_payments',
148
+			),
149
+
150
+		);
151
+	}
152
+
153
+
154
+	protected function _set_page_config()
155
+	{
156
+		$this->_page_config = array(
157
+			'default'          => array(
158
+				'nav'           => array(
159
+					'label' => esc_html__('Overview', 'event_espresso'),
160
+					'order' => 10,
161
+				),
162
+				'list_table'    => 'EE_Admin_Transactions_List_Table',
163
+				'help_tabs'     => array(
164
+					'transactions_overview_help_tab'                       => array(
165
+						'title'    => esc_html__('Transactions Overview', 'event_espresso'),
166
+						'filename' => 'transactions_overview',
167
+					),
168
+					'transactions_overview_table_column_headings_help_tab' => array(
169
+						'title'    => esc_html__('Transactions Table Column Headings', 'event_espresso'),
170
+						'filename' => 'transactions_overview_table_column_headings',
171
+					),
172
+					'transactions_overview_views_filters_help_tab'         => array(
173
+						'title'    => esc_html__('Transaction Views & Filters & Search', 'event_espresso'),
174
+						'filename' => 'transactions_overview_views_filters_search',
175
+					),
176
+				),
177
+				'help_tour'     => array('Transactions_Overview_Help_Tour'),
178
+				/**
179
+				 * commented out because currently we are not displaying tips for transaction list table status but this
180
+				 * may change in a later iteration so want to keep the code for then.
181
+				 */
182
+				// 'qtips' => array( 'Transactions_List_Table_Tips' ),
183
+				'require_nonce' => false,
184
+			),
185
+			'view_transaction' => array(
186
+				'nav'       => array(
187
+					'label'      => esc_html__('View Transaction', 'event_espresso'),
188
+					'order'      => 5,
189
+					'url'        => isset($this->_req_data['TXN_ID'])
190
+						? add_query_arg(array('TXN_ID' => $this->_req_data['TXN_ID']), $this->_current_page_view_url)
191
+						: $this->_admin_base_url,
192
+					'persistent' => false,
193
+				),
194
+				'help_tabs' => array(
195
+					'transactions_view_transaction_help_tab'                                              => array(
196
+						'title'    => esc_html__('View Transaction', 'event_espresso'),
197
+						'filename' => 'transactions_view_transaction',
198
+					),
199
+					'transactions_view_transaction_transaction_details_table_help_tab'                    => array(
200
+						'title'    => esc_html__('Transaction Details Table', 'event_espresso'),
201
+						'filename' => 'transactions_view_transaction_transaction_details_table',
202
+					),
203
+					'transactions_view_transaction_attendees_registered_help_tab'                         => array(
204
+						'title'    => esc_html__('Attendees Registered', 'event_espresso'),
205
+						'filename' => 'transactions_view_transaction_attendees_registered',
206
+					),
207
+					'transactions_view_transaction_views_primary_registrant_billing_information_help_tab' => array(
208
+						'title'    => esc_html__('Primary Registrant & Billing Information', 'event_espresso'),
209
+						'filename' => 'transactions_view_transaction_primary_registrant_billing_information',
210
+					),
211
+				),
212
+				'qtips'     => array('Transaction_Details_Tips'),
213
+				'help_tour' => array('Transaction_Details_Help_Tour'),
214
+				'metaboxes' => array('_transaction_details_metaboxes'),
215
+
216
+				'require_nonce' => false,
217
+			),
218
+		);
219
+	}
220
+
221
+
222
+	/**
223
+	 * The below methods aren't used by this class currently
224
+	 */
225
+	protected function _add_screen_options()
226
+	{
227
+		// noop
228
+	}
229
+
230
+
231
+	protected function _add_feature_pointers()
232
+	{
233
+		// noop
234
+	}
235
+
236
+
237
+	public function admin_init()
238
+	{
239
+		// IF a registration was JUST added via the admin...
240
+		if (isset(
241
+			$this->_req_data['redirect_from'],
242
+			$this->_req_data['EVT_ID'],
243
+			$this->_req_data['event_name']
244
+		)) {
245
+			// then set a cookie so that we can block any attempts to use
246
+			// the back button as a way to enter another registration.
247
+			setcookie(
248
+				'ee_registration_added',
249
+				$this->_req_data['EVT_ID'],
250
+				time() + WEEK_IN_SECONDS,
251
+				'/'
252
+			);
253
+			// and update the global
254
+			$_COOKIE['ee_registration_added'] = $this->_req_data['EVT_ID'];
255
+		}
256
+		EE_Registry::$i18n_js_strings['invalid_server_response'] = esc_html__(
257
+			'An error occurred! Your request may have been processed, but a valid response from the server was not received. Please refresh the page and try again.',
258
+			'event_espresso'
259
+		);
260
+		EE_Registry::$i18n_js_strings['error_occurred'] = esc_html__(
261
+			'An error occurred! Please refresh the page and try again.',
262
+			'event_espresso'
263
+		);
264
+		EE_Registry::$i18n_js_strings['txn_status_array'] = self::$_txn_status;
265
+		EE_Registry::$i18n_js_strings['pay_status_array'] = self::$_pay_status;
266
+		EE_Registry::$i18n_js_strings['payments_total'] = esc_html__('Payments Total', 'event_espresso');
267
+		EE_Registry::$i18n_js_strings['transaction_overpaid'] = esc_html__(
268
+			'This transaction has been overpaid ! Payments Total',
269
+			'event_espresso'
270
+		);
271
+	}
272
+
273
+
274
+	public function admin_notices()
275
+	{
276
+		// noop
277
+	}
278
+
279
+
280
+	public function admin_footer_scripts()
281
+	{
282
+		// noop
283
+	}
284
+
285
+
286
+	/**
287
+	 * _set_transaction_status_array
288
+	 * sets list of transaction statuses
289
+	 *
290
+	 * @access private
291
+	 * @return void
292
+	 * @throws EE_Error
293
+	 * @throws InvalidArgumentException
294
+	 * @throws InvalidDataTypeException
295
+	 * @throws InvalidInterfaceException
296
+	 */
297
+	private function _set_transaction_status_array()
298
+	{
299
+		self::$_txn_status = EEM_Transaction::instance()->status_array(true);
300
+	}
301
+
302
+
303
+	/**
304
+	 * get_transaction_status_array
305
+	 * return the transaction status array for wp_list_table
306
+	 *
307
+	 * @access public
308
+	 * @return array
309
+	 */
310
+	public function get_transaction_status_array()
311
+	{
312
+		return self::$_txn_status;
313
+	}
314
+
315
+
316
+	/**
317
+	 *    get list of payment statuses
318
+	 *
319
+	 * @access private
320
+	 * @return void
321
+	 * @throws EE_Error
322
+	 * @throws InvalidArgumentException
323
+	 * @throws InvalidDataTypeException
324
+	 * @throws InvalidInterfaceException
325
+	 */
326
+	private function _get_payment_status_array()
327
+	{
328
+		self::$_pay_status = EEM_Payment::instance()->status_array(true);
329
+		$this->_template_args['payment_status'] = self::$_pay_status;
330
+	}
331
+
332
+
333
+	/**
334
+	 *    _add_screen_options_default
335
+	 *
336
+	 * @access protected
337
+	 * @return void
338
+	 * @throws InvalidArgumentException
339
+	 * @throws InvalidDataTypeException
340
+	 * @throws InvalidInterfaceException
341
+	 */
342
+	protected function _add_screen_options_default()
343
+	{
344
+		$this->_per_page_screen_option();
345
+	}
346
+
347
+
348
+	/**
349
+	 * load_scripts_styles
350
+	 *
351
+	 * @access public
352
+	 * @return void
353
+	 */
354
+	public function load_scripts_styles()
355
+	{
356
+		// enqueue style
357
+		wp_register_style(
358
+			'espresso_txn',
359
+			TXN_ASSETS_URL . 'espresso_transactions_admin.css',
360
+			array(),
361
+			EVENT_ESPRESSO_VERSION
362
+		);
363
+		wp_enqueue_style('espresso_txn');
364
+		// scripts
365
+		wp_register_script(
366
+			'espresso_txn',
367
+			TXN_ASSETS_URL . 'espresso_transactions_admin.js',
368
+			array(
369
+				'ee_admin_js',
370
+				'ee-datepicker',
371
+				'jquery-ui-datepicker',
372
+				'jquery-ui-draggable',
373
+				'ee-dialog',
374
+				'ee-accounting',
375
+				'ee-serialize-full-array',
376
+			),
377
+			EVENT_ESPRESSO_VERSION,
378
+			true
379
+		);
380
+		wp_enqueue_script('espresso_txn');
381
+	}
382
+
383
+
384
+	/**
385
+	 *    load_scripts_styles_view_transaction
386
+	 *
387
+	 * @access public
388
+	 * @return void
389
+	 */
390
+	public function load_scripts_styles_view_transaction()
391
+	{
392
+		// styles
393
+		wp_enqueue_style('espresso-ui-theme');
394
+	}
395
+
396
+
397
+	/**
398
+	 *    load_scripts_styles_default
399
+	 *
400
+	 * @access public
401
+	 * @return void
402
+	 */
403
+	public function load_scripts_styles_default()
404
+	{
405
+		// styles
406
+		wp_enqueue_style('espresso-ui-theme');
407
+	}
408
+
409
+
410
+	/**
411
+	 *    _set_list_table_views_default
412
+	 *
413
+	 * @access protected
414
+	 * @return void
415
+	 */
416
+	protected function _set_list_table_views_default()
417
+	{
418
+		$this->_views = array(
419
+			'all'        => array(
420
+				'slug'  => 'all',
421
+				'label' => esc_html__('View All Transactions', 'event_espresso'),
422
+				'count' => 0,
423
+			),
424
+			'abandoned'  => array(
425
+				'slug'  => 'abandoned',
426
+				'label' => esc_html__('Abandoned Transactions', 'event_espresso'),
427
+				'count' => 0,
428
+			),
429
+			'incomplete' => array(
430
+				'slug'  => 'incomplete',
431
+				'label' => esc_html__('Incomplete Transactions', 'event_espresso'),
432
+				'count' => 0,
433
+			),
434
+		);
435
+		if (/**
436
+		 * Filters whether a link to the "Failed Transactions" list table
437
+		 * appears on the Transactions Admin Page list table.
438
+		 * List display can be turned back on via the following:
439
+		 * add_filter(
440
+		 *     'FHEE__Transactions_Admin_Page___set_list_table_views_default__display_failed_txns_list',
441
+		 *     '__return_true'
442
+		 * );
443
+		 *
444
+		 * @since 4.9.70.p
445
+		 * @param boolean                 $display_failed_txns_list
446
+		 * @param Transactions_Admin_Page $this
447
+		 */
448
+		apply_filters(
449
+			'FHEE__Transactions_Admin_Page___set_list_table_views_default__display_failed_txns_list',
450
+			false,
451
+			$this
452
+		)
453
+		) {
454
+			$this->_views['failed'] = array(
455
+				'slug'  => 'failed',
456
+				'label' => esc_html__('Failed Transactions', 'event_espresso'),
457
+				'count' => 0,
458
+			);
459
+		}
460
+	}
461
+
462
+
463
+	/**
464
+	 * _set_transaction_object
465
+	 * This sets the _transaction property for the transaction details screen
466
+	 *
467
+	 * @access private
468
+	 * @return void
469
+	 * @throws EE_Error
470
+	 * @throws InvalidArgumentException
471
+	 * @throws RuntimeException
472
+	 * @throws InvalidDataTypeException
473
+	 * @throws InvalidInterfaceException
474
+	 * @throws ReflectionException
475
+	 */
476
+	private function _set_transaction_object()
477
+	{
478
+		if ($this->_transaction instanceof EE_Transaction) {
479
+			return;
480
+		} //get out we've already set the object
481
+
482
+		$TXN_ID = ! empty($this->_req_data['TXN_ID'])
483
+			? absint($this->_req_data['TXN_ID'])
484
+			: false;
485
+
486
+		// get transaction object
487
+		$this->_transaction = EEM_Transaction::instance()->get_one_by_ID($TXN_ID);
488
+		$this->_session = $this->_transaction instanceof EE_Transaction
489
+			? $this->_transaction->session_data()
490
+			: null;
491
+		if ($this->_transaction instanceof EE_Transaction) {
492
+			$this->_transaction->verify_abandoned_transaction_status();
493
+		}
494
+
495
+		if (! $this->_transaction instanceof EE_Transaction) {
496
+			$error_msg = sprintf(
497
+				esc_html__(
498
+					'An error occurred and the details for the transaction with the ID # %d could not be retrieved.',
499
+					'event_espresso'
500
+				),
501
+				$TXN_ID
502
+			);
503
+			EE_Error::add_error($error_msg, __FILE__, __FUNCTION__, __LINE__);
504
+		}
505
+	}
506
+
507
+
508
+	/**
509
+	 *    _transaction_legend_items
510
+	 *
511
+	 * @access protected
512
+	 * @return array
513
+	 * @throws EE_Error
514
+	 * @throws InvalidArgumentException
515
+	 * @throws ReflectionException
516
+	 * @throws InvalidDataTypeException
517
+	 * @throws InvalidInterfaceException
518
+	 */
519
+	protected function _transaction_legend_items()
520
+	{
521
+		EE_Registry::instance()->load_helper('MSG_Template');
522
+		$items = array();
523
+
524
+		if (EE_Registry::instance()->CAP->current_user_can(
525
+			'ee_read_global_messages',
526
+			'view_filtered_messages'
527
+		)) {
528
+			$related_for_icon = EEH_MSG_Template::get_message_action_icon('see_notifications_for');
529
+			if (is_array($related_for_icon)
530
+				&& isset($related_for_icon['css_class'], $related_for_icon['label'])
531
+			) {
532
+				$items['view_related_messages'] = array(
533
+					'class' => $related_for_icon['css_class'],
534
+					'desc'  => $related_for_icon['label'],
535
+				);
536
+			}
537
+		}
538
+
539
+		$items = apply_filters(
540
+			'FHEE__Transactions_Admin_Page___transaction_legend_items__items',
541
+			array_merge(
542
+				$items,
543
+				array(
544
+					'view_details'          => array(
545
+						'class' => 'dashicons dashicons-cart',
546
+						'desc'  => esc_html__('View Transaction Details', 'event_espresso'),
547
+					),
548
+					'view_invoice'          => array(
549
+						'class' => 'dashicons dashicons-media-spreadsheet',
550
+						'desc'  => esc_html__('View Transaction Invoice', 'event_espresso'),
551
+					),
552
+					'view_receipt'          => array(
553
+						'class' => 'dashicons dashicons-media-default',
554
+						'desc'  => esc_html__('View Transaction Receipt', 'event_espresso'),
555
+					),
556
+					'view_registration'     => array(
557
+						'class' => 'dashicons dashicons-clipboard',
558
+						'desc'  => esc_html__('View Registration Details', 'event_espresso'),
559
+					),
560
+					'payment_overview_link' => array(
561
+						'class' => 'dashicons dashicons-money',
562
+						'desc'  => esc_html__('Make Payment on Frontend', 'event_espresso'),
563
+					),
564
+				)
565
+			)
566
+		);
567
+
568
+		if (EEH_MSG_Template::is_mt_active('payment_reminder')
569
+			&& EE_Registry::instance()->CAP->current_user_can(
570
+				'ee_send_message',
571
+				'espresso_transactions_send_payment_reminder'
572
+			)
573
+		) {
574
+			$items['send_payment_reminder'] = array(
575
+				'class' => 'dashicons dashicons-email-alt',
576
+				'desc'  => esc_html__('Send Payment Reminder', 'event_espresso'),
577
+			);
578
+		} else {
579
+			$items['blank*'] = array(
580
+				'class' => '',
581
+				'desc'  => '',
582
+			);
583
+		}
584
+		$more_items = apply_filters(
585
+			'FHEE__Transactions_Admin_Page___transaction_legend_items__more_items',
586
+			array(
587
+				'overpaid'   => array(
588
+					'class' => 'ee-status-legend ee-status-legend-' . EEM_Transaction::overpaid_status_code,
589
+					'desc'  => EEH_Template::pretty_status(
590
+						EEM_Transaction::overpaid_status_code,
591
+						false,
592
+						'sentence'
593
+					),
594
+				),
595
+				'complete'   => array(
596
+					'class' => 'ee-status-legend ee-status-legend-' . EEM_Transaction::complete_status_code,
597
+					'desc'  => EEH_Template::pretty_status(
598
+						EEM_Transaction::complete_status_code,
599
+						false,
600
+						'sentence'
601
+					),
602
+				),
603
+				'incomplete' => array(
604
+					'class' => 'ee-status-legend ee-status-legend-' . EEM_Transaction::incomplete_status_code,
605
+					'desc'  => EEH_Template::pretty_status(
606
+						EEM_Transaction::incomplete_status_code,
607
+						false,
608
+						'sentence'
609
+					),
610
+				),
611
+				'abandoned'  => array(
612
+					'class' => 'ee-status-legend ee-status-legend-' . EEM_Transaction::abandoned_status_code,
613
+					'desc'  => EEH_Template::pretty_status(
614
+						EEM_Transaction::abandoned_status_code,
615
+						false,
616
+						'sentence'
617
+					),
618
+				),
619
+				'failed'     => array(
620
+					'class' => 'ee-status-legend ee-status-legend-' . EEM_Transaction::failed_status_code,
621
+					'desc'  => EEH_Template::pretty_status(
622
+						EEM_Transaction::failed_status_code,
623
+						false,
624
+						'sentence'
625
+					),
626
+				),
627
+			)
628
+		);
629
+
630
+		return array_merge($items, $more_items);
631
+	}
632
+
633
+
634
+	/**
635
+	 *    _transactions_overview_list_table
636
+	 *
637
+	 * @access protected
638
+	 * @return void
639
+	 * @throws DomainException
640
+	 * @throws EE_Error
641
+	 * @throws InvalidArgumentException
642
+	 * @throws InvalidDataTypeException
643
+	 * @throws InvalidInterfaceException
644
+	 * @throws ReflectionException
645
+	 */
646
+	protected function _transactions_overview_list_table()
647
+	{
648
+		$this->_admin_page_title = esc_html__('Transactions', 'event_espresso');
649
+		$event = isset($this->_req_data['EVT_ID'])
650
+			? EEM_Event::instance()->get_one_by_ID($this->_req_data['EVT_ID'])
651
+			: null;
652
+		$this->_template_args['admin_page_header'] = $event instanceof EE_Event
653
+			? sprintf(
654
+				esc_html__(
655
+					'%sViewing Transactions for the Event: %s%s',
656
+					'event_espresso'
657
+				),
658
+				'<h3>',
659
+				'<a href="'
660
+				. EE_Admin_Page::add_query_args_and_nonce(
661
+					array('action' => 'edit', 'post' => $event->ID()),
662
+					EVENTS_ADMIN_URL
663
+				)
664
+				. '" title="'
665
+				. esc_attr__(
666
+					'Click to Edit event',
667
+					'event_espresso'
668
+				)
669
+				. '">' . $event->name() . '</a>',
670
+				'</h3>'
671
+			)
672
+			: '';
673
+		$this->_template_args['after_list_table'] = $this->_display_legend($this->_transaction_legend_items());
674
+		$this->display_admin_list_table_page_with_no_sidebar();
675
+	}
676
+
677
+
678
+	/**
679
+	 *    _transaction_details
680
+	 * generates HTML for the View Transaction Details Admin page
681
+	 *
682
+	 * @access protected
683
+	 * @return void
684
+	 * @throws DomainException
685
+	 * @throws EE_Error
686
+	 * @throws InvalidArgumentException
687
+	 * @throws InvalidDataTypeException
688
+	 * @throws InvalidInterfaceException
689
+	 * @throws RuntimeException
690
+	 * @throws ReflectionException
691
+	 */
692
+	protected function _transaction_details()
693
+	{
694
+		do_action('AHEE__Transactions_Admin_Page__transaction_details__start', $this->_transaction);
695
+
696
+		$this->_set_transaction_status_array();
697
+
698
+		$this->_template_args = array();
699
+		$this->_template_args['transactions_page'] = $this->_wp_page_slug;
700
+
701
+		$this->_set_transaction_object();
702
+
703
+		if (! $this->_transaction instanceof EE_Transaction) {
704
+			return;
705
+		}
706
+		$primary_registration = $this->_transaction->primary_registration();
707
+		$attendee = $primary_registration instanceof EE_Registration
708
+			? $primary_registration->attendee()
709
+			: null;
710
+
711
+		$this->_template_args['txn_nmbr']['value'] = $this->_transaction->ID();
712
+		$this->_template_args['txn_nmbr']['label'] = esc_html__('Transaction Number', 'event_espresso');
713
+
714
+		$this->_template_args['txn_datetime']['value'] = $this->_transaction->get_i18n_datetime('TXN_timestamp');
715
+		$this->_template_args['txn_datetime']['label'] = esc_html__('Date', 'event_espresso');
716
+
717
+		$this->_template_args['txn_status']['value'] = self::$_txn_status[ $this->_transaction->status_ID() ];
718
+		$this->_template_args['txn_status']['label'] = esc_html__('Transaction Status', 'event_espresso');
719
+		$this->_template_args['txn_status']['class'] = 'status-' . $this->_transaction->status_ID();
720
+
721
+		$this->_template_args['grand_total'] = $this->_transaction->total();
722
+		$this->_template_args['total_paid'] = $this->_transaction->paid();
723
+
724
+		$amount_due = $this->_transaction->total() - $this->_transaction->paid();
725
+		$this->_template_args['amount_due'] = EEH_Template::format_currency(
726
+			$amount_due,
727
+			true
728
+		);
729
+		if (EE_Registry::instance()->CFG->currency->sign_b4) {
730
+			$this->_template_args['amount_due'] = EE_Registry::instance()->CFG->currency->sign
731
+												  . $this->_template_args['amount_due'];
732
+		} else {
733
+			$this->_template_args['amount_due'] .= EE_Registry::instance()->CFG->currency->sign;
734
+		}
735
+		$this->_template_args['amount_due_class'] = '';
736
+
737
+		if ($this->_transaction->paid() === $this->_transaction->total()) {
738
+			// paid in full
739
+			$this->_template_args['amount_due'] = false;
740
+		} elseif ($this->_transaction->paid() > $this->_transaction->total()) {
741
+			// overpaid
742
+			$this->_template_args['amount_due_class'] = 'txn-overview-no-payment-spn';
743
+		} elseif ($this->_transaction->total() > (float) 0) {
744
+			if ($this->_transaction->paid() > (float) 0) {
745
+				// monies owing
746
+				$this->_template_args['amount_due_class'] = 'txn-overview-part-payment-spn';
747
+			} elseif ($this->_transaction->paid() === (float) 0) {
748
+				// no payments made yet
749
+				$this->_template_args['amount_due_class'] = 'txn-overview-no-payment-spn';
750
+			}
751
+		} elseif ($this->_transaction->total() === (float) 0) {
752
+			// free event
753
+			$this->_template_args['amount_due'] = false;
754
+		}
755
+
756
+		$payment_method = $this->_transaction->payment_method();
757
+
758
+		$this->_template_args['method_of_payment_name'] = $payment_method instanceof EE_Payment_Method
759
+			? $payment_method->admin_name()
760
+			: esc_html__('Unknown', 'event_espresso');
761
+
762
+		$this->_template_args['currency_sign'] = EE_Registry::instance()->CFG->currency->sign;
763
+		// link back to overview
764
+		$this->_template_args['txn_overview_url'] = ! empty($_SERVER['HTTP_REFERER'])
765
+			? $_SERVER['HTTP_REFERER']
766
+			: TXN_ADMIN_URL;
767
+
768
+
769
+		// next link
770
+		$next_txn = $this->_transaction->next(
771
+			null,
772
+			array(array('STS_ID' => array('!=', EEM_Transaction::failed_status_code))),
773
+			'TXN_ID'
774
+		);
775
+		$this->_template_args['next_transaction'] = $next_txn
776
+			? $this->_next_link(
777
+				EE_Admin_Page::add_query_args_and_nonce(
778
+					array('action' => 'view_transaction', 'TXN_ID' => $next_txn['TXN_ID']),
779
+					TXN_ADMIN_URL
780
+				),
781
+				'dashicons dashicons-arrow-right ee-icon-size-22'
782
+			)
783
+			: '';
784
+		// previous link
785
+		$previous_txn = $this->_transaction->previous(
786
+			null,
787
+			array(array('STS_ID' => array('!=', EEM_Transaction::failed_status_code))),
788
+			'TXN_ID'
789
+		);
790
+		$this->_template_args['previous_transaction'] = $previous_txn
791
+			? $this->_previous_link(
792
+				EE_Admin_Page::add_query_args_and_nonce(
793
+					array('action' => 'view_transaction', 'TXN_ID' => $previous_txn['TXN_ID']),
794
+					TXN_ADMIN_URL
795
+				),
796
+				'dashicons dashicons-arrow-left ee-icon-size-22'
797
+			)
798
+			: '';
799
+
800
+		// were we just redirected here after adding a new registration ???
801
+		if (isset(
802
+			$this->_req_data['redirect_from'],
803
+			$this->_req_data['EVT_ID'],
804
+			$this->_req_data['event_name']
805
+		)) {
806
+			if (EE_Registry::instance()->CAP->current_user_can(
807
+				'ee_edit_registrations',
808
+				'espresso_registrations_new_registration',
809
+				$this->_req_data['EVT_ID']
810
+			)) {
811
+				$this->_admin_page_title .= '<a id="add-new-registration" class="add-new-h2 button-primary" href="';
812
+				$this->_admin_page_title .= EE_Admin_Page::add_query_args_and_nonce(
813
+					array(
814
+						'page'     => 'espresso_registrations',
815
+						'action'   => 'new_registration',
816
+						'return'   => 'default',
817
+						'TXN_ID'   => $this->_transaction->ID(),
818
+						'event_id' => $this->_req_data['EVT_ID'],
819
+					),
820
+					REG_ADMIN_URL
821
+				);
822
+				$this->_admin_page_title .= '">';
823
+
824
+				$this->_admin_page_title .= sprintf(
825
+					esc_html__('Add Another New Registration to Event: "%1$s" ?', 'event_espresso'),
826
+					htmlentities(urldecode($this->_req_data['event_name']), ENT_QUOTES, 'UTF-8')
827
+				);
828
+				$this->_admin_page_title .= '</a>';
829
+			}
830
+			EE_Registry::instance()->SSN->clear_session(__CLASS__, __FUNCTION__);
831
+		}
832
+		// grab messages at the last second
833
+		$this->_template_args['notices'] = EE_Error::get_notices();
834
+		// path to template
835
+		$template_path = TXN_TEMPLATE_PATH . 'txn_admin_details_header.template.php';
836
+		$this->_template_args['admin_page_header'] = EEH_Template::display_template(
837
+			$template_path,
838
+			$this->_template_args,
839
+			true
840
+		);
841
+
842
+		// the details template wrapper
843
+		$this->display_admin_page_with_sidebar();
844
+	}
845
+
846
+
847
+	/**
848
+	 *        _transaction_details_metaboxes
849
+	 *
850
+	 * @access protected
851
+	 * @return void
852
+	 * @throws EE_Error
853
+	 * @throws InvalidArgumentException
854
+	 * @throws InvalidDataTypeException
855
+	 * @throws InvalidInterfaceException
856
+	 * @throws RuntimeException
857
+	 * @throws ReflectionException
858
+	 */
859
+	protected function _transaction_details_metaboxes()
860
+	{
861
+
862
+		$this->_set_transaction_object();
863
+
864
+		if (! $this->_transaction instanceof EE_Transaction) {
865
+			return;
866
+		}
867
+		add_meta_box(
868
+			'edit-txn-details-mbox',
869
+			esc_html__('Transaction Details', 'event_espresso'),
870
+			array($this, 'txn_details_meta_box'),
871
+			$this->_wp_page_slug,
872
+			'normal',
873
+			'high'
874
+		);
875
+		add_meta_box(
876
+			'edit-txn-attendees-mbox',
877
+			esc_html__('Attendees Registered in this Transaction', 'event_espresso'),
878
+			array($this, 'txn_attendees_meta_box'),
879
+			$this->_wp_page_slug,
880
+			'normal',
881
+			'high',
882
+			array('TXN_ID' => $this->_transaction->ID())
883
+		);
884
+		add_meta_box(
885
+			'edit-txn-registrant-mbox',
886
+			esc_html__('Primary Contact', 'event_espresso'),
887
+			array($this, 'txn_registrant_side_meta_box'),
888
+			$this->_wp_page_slug,
889
+			'side',
890
+			'high'
891
+		);
892
+		add_meta_box(
893
+			'edit-txn-billing-info-mbox',
894
+			esc_html__('Billing Information', 'event_espresso'),
895
+			array($this, 'txn_billing_info_side_meta_box'),
896
+			$this->_wp_page_slug,
897
+			'side',
898
+			'high'
899
+		);
900
+	}
901
+
902
+
903
+	/**
904
+	 * Callback for transaction actions metabox.
905
+	 *
906
+	 * @param EE_Transaction|null $transaction
907
+	 * @return string
908
+	 * @throws DomainException
909
+	 * @throws EE_Error
910
+	 * @throws InvalidArgumentException
911
+	 * @throws InvalidDataTypeException
912
+	 * @throws InvalidInterfaceException
913
+	 * @throws ReflectionException
914
+	 * @throws RuntimeException
915
+	 */
916
+	public function getActionButtons(EE_Transaction $transaction = null)
917
+	{
918
+		$content = '';
919
+		$actions = array();
920
+		if (! $transaction instanceof EE_Transaction) {
921
+			return $content;
922
+		}
923
+		/** @var EE_Registration $primary_registration */
924
+		$primary_registration = $transaction->primary_registration();
925
+		$attendee = $primary_registration instanceof EE_Registration
926
+			? $primary_registration->attendee()
927
+			: null;
928
+
929
+		if ($attendee instanceof EE_Attendee
930
+			&& EE_Registry::instance()->CAP->current_user_can(
931
+				'ee_send_message',
932
+				'espresso_transactions_send_payment_reminder'
933
+			)
934
+		) {
935
+			$actions['payment_reminder'] =
936
+				EEH_MSG_Template::is_mt_active('payment_reminder')
937
+				&& $this->_transaction->status_ID() !== EEM_Transaction::complete_status_code
938
+				&& $this->_transaction->status_ID() !== EEM_Transaction::overpaid_status_code
939
+					? EEH_Template::get_button_or_link(
940
+						EE_Admin_Page::add_query_args_and_nonce(
941
+							array(
942
+								'action'      => 'send_payment_reminder',
943
+								'TXN_ID'      => $this->_transaction->ID(),
944
+								'redirect_to' => 'view_transaction',
945
+							),
946
+							TXN_ADMIN_URL
947
+						),
948
+						esc_html__(' Send Payment Reminder', 'event_espresso'),
949
+						'button secondary-button',
950
+						'dashicons dashicons-email-alt'
951
+					)
952
+					: '';
953
+		}
954
+
955
+		if (EE_Registry::instance()->CAP->current_user_can(
956
+			'ee_edit_payments',
957
+			'espresso_transactions_recalculate_line_items'
958
+		)
959
+		) {
960
+			$actions['recalculate_line_items'] = EEH_Template::get_button_or_link(
961
+				EE_Admin_Page::add_query_args_and_nonce(
962
+					array(
963
+						'action'      => 'espresso_recalculate_line_items',
964
+						'TXN_ID'      => $this->_transaction->ID(),
965
+						'redirect_to' => 'view_transaction',
966
+					),
967
+					TXN_ADMIN_URL
968
+				),
969
+				esc_html__(' Recalculate Taxes and Total', 'event_espresso'),
970
+				'button secondary-button',
971
+				'dashicons dashicons-update'
972
+			);
973
+		}
974
+
975
+		if ($primary_registration instanceof EE_Registration
976
+			&& EEH_MSG_Template::is_mt_active('receipt')
977
+		) {
978
+			$actions['receipt'] = EEH_Template::get_button_or_link(
979
+				$primary_registration->receipt_url(),
980
+				esc_html__('View Receipt', 'event_espresso'),
981
+				'button secondary-button',
982
+				'dashicons dashicons-media-default'
983
+			);
984
+		}
985
+
986
+		if ($primary_registration instanceof EE_Registration
987
+			&& EEH_MSG_Template::is_mt_active('invoice')
988
+		) {
989
+			$actions['invoice'] = EEH_Template::get_button_or_link(
990
+				$primary_registration->invoice_url(),
991
+				esc_html__('View Invoice', 'event_espresso'),
992
+				'button secondary-button',
993
+				'dashicons dashicons-media-spreadsheet'
994
+			);
995
+		}
996
+		$actions = array_filter(
997
+			apply_filters('FHEE__Transactions_Admin_Page__getActionButtons__actions', $actions, $transaction)
998
+		);
999
+		if ($actions) {
1000
+			$content = '<ul>';
1001
+			$content .= '<li>' . implode('</li><li>', $actions) . '</li>';
1002
+			$content .= '</uL>';
1003
+		}
1004
+		return $content;
1005
+	}
1006
+
1007
+
1008
+	/**
1009
+	 * txn_details_meta_box
1010
+	 * generates HTML for the Transaction main meta box
1011
+	 *
1012
+	 * @return void
1013
+	 * @throws DomainException
1014
+	 * @throws EE_Error
1015
+	 * @throws InvalidArgumentException
1016
+	 * @throws InvalidDataTypeException
1017
+	 * @throws InvalidInterfaceException
1018
+	 * @throws RuntimeException
1019
+	 * @throws ReflectionException
1020
+	 */
1021
+	public function txn_details_meta_box()
1022
+	{
1023
+		$this->_set_transaction_object();
1024
+		$this->_template_args['TXN_ID'] = $this->_transaction->ID();
1025
+		$this->_template_args['attendee'] = $this->_transaction->primary_registration() instanceof EE_Registration
1026
+			? $this->_transaction->primary_registration()->attendee()
1027
+			: null;
1028
+		$this->_template_args['can_edit_payments'] = EE_Registry::instance()->CAP->current_user_can(
1029
+			'ee_edit_payments',
1030
+			'apply_payment_or_refund_from_registration_details'
1031
+		);
1032
+		$this->_template_args['can_delete_payments'] = EE_Registry::instance()->CAP->current_user_can(
1033
+			'ee_delete_payments',
1034
+			'delete_payment_from_registration_details'
1035
+		);
1036
+
1037
+		// get line table
1038
+		EEH_Autoloader::register_line_item_display_autoloaders();
1039
+		$Line_Item_Display = new EE_Line_Item_Display(
1040
+			'admin_table',
1041
+			'EE_Admin_Table_Line_Item_Display_Strategy'
1042
+		);
1043
+		$this->_template_args['line_item_table'] = $Line_Item_Display->display_line_item(
1044
+			$this->_transaction->total_line_item()
1045
+		);
1046
+		$this->_template_args['REG_code'] = $this->_transaction->primary_registration()->reg_code();
1047
+
1048
+		// process taxes
1049
+		$taxes = $this->_transaction->line_items(array(array('LIN_type' => EEM_Line_Item::type_tax)));
1050
+		$this->_template_args['taxes'] = ! empty($taxes) ? $taxes : false;
1051
+
1052
+		$this->_template_args['grand_total'] = EEH_Template::format_currency(
1053
+			$this->_transaction->total(),
1054
+			false,
1055
+			false
1056
+		);
1057
+		$this->_template_args['grand_raw_total'] = $this->_transaction->total();
1058
+		$this->_template_args['TXN_status'] = $this->_transaction->status_ID();
1059
+
1060
+		// process payment details
1061
+		$payments = $this->_transaction->payments();
1062
+		if (! empty($payments)) {
1063
+			$this->_template_args['payments'] = $payments;
1064
+			$this->_template_args['existing_reg_payments'] = $this->_get_registration_payment_IDs($payments);
1065
+		} else {
1066
+			$this->_template_args['payments'] = false;
1067
+			$this->_template_args['existing_reg_payments'] = array();
1068
+		}
1069
+
1070
+		$this->_template_args['edit_payment_url'] = add_query_arg(array('action' => 'edit_payment'), TXN_ADMIN_URL);
1071
+		$this->_template_args['delete_payment_url'] = add_query_arg(
1072
+			array('action' => 'espresso_delete_payment'),
1073
+			TXN_ADMIN_URL
1074
+		);
1075
+
1076
+		if (isset($txn_details['invoice_number'])) {
1077
+			$this->_template_args['txn_details']['invoice_number']['value'] = $this->_template_args['REG_code'];
1078
+			$this->_template_args['txn_details']['invoice_number']['label'] = esc_html__(
1079
+				'Invoice Number',
1080
+				'event_espresso'
1081
+			);
1082
+		}
1083
+
1084
+		$this->_template_args['txn_details']['registration_session']['value'] = $this->_transaction
1085
+			->primary_registration()
1086
+			->session_ID();
1087
+		$this->_template_args['txn_details']['registration_session']['label'] = esc_html__(
1088
+			'Registration Session',
1089
+			'event_espresso'
1090
+		);
1091
+
1092
+		$this->_template_args['txn_details']['ip_address']['value'] = isset($this->_session['ip_address'])
1093
+			? $this->_session['ip_address']
1094
+			: '';
1095
+		$this->_template_args['txn_details']['ip_address']['label'] = esc_html__(
1096
+			'Transaction placed from IP',
1097
+			'event_espresso'
1098
+		);
1099
+
1100
+		$this->_template_args['txn_details']['user_agent']['value'] = isset($this->_session['user_agent'])
1101
+			? $this->_session['user_agent']
1102
+			: '';
1103
+		$this->_template_args['txn_details']['user_agent']['label'] = esc_html__(
1104
+			'Registrant User Agent',
1105
+			'event_espresso'
1106
+		);
1107
+
1108
+		$reg_steps = '<ul>';
1109
+		foreach ($this->_transaction->reg_steps() as $reg_step => $reg_step_status) {
1110
+			if ($reg_step_status === true) {
1111
+				$reg_steps .= '<li style="color:#70cc50">'
1112
+							  . sprintf(
1113
+								  esc_html__('%1$s : Completed', 'event_espresso'),
1114
+								  ucwords(str_replace('_', ' ', $reg_step))
1115
+							  )
1116
+							  . '</li>';
1117
+			} elseif (is_numeric($reg_step_status) && $reg_step_status !== false) {
1118
+				$reg_steps .= '<li style="color:#2EA2CC">'
1119
+							  . sprintf(
1120
+								  esc_html__('%1$s : Initiated %2$s', 'event_espresso'),
1121
+								  ucwords(str_replace('_', ' ', $reg_step)),
1122
+								  date(
1123
+									  get_option('date_format') . ' ' . get_option('time_format'),
1124
+									  $reg_step_status + (get_option('gmt_offset') * HOUR_IN_SECONDS)
1125
+								  )
1126
+							  )
1127
+							  . '</li>';
1128
+			} else {
1129
+				$reg_steps .= '<li style="color:#E76700">'
1130
+							  . sprintf(
1131
+								  esc_html__('%1$s : Never Initiated', 'event_espresso'),
1132
+								  ucwords(str_replace('_', ' ', $reg_step))
1133
+							  )
1134
+							  . '</li>';
1135
+			}
1136
+		}
1137
+		$reg_steps .= '</ul>';
1138
+		$this->_template_args['txn_details']['reg_steps']['value'] = $reg_steps;
1139
+		$this->_template_args['txn_details']['reg_steps']['label'] = esc_html__(
1140
+			'Registration Step Progress',
1141
+			'event_espresso'
1142
+		);
1143
+
1144
+
1145
+		$this->_get_registrations_to_apply_payment_to();
1146
+		$this->_get_payment_methods($payments);
1147
+		$this->_get_payment_status_array();
1148
+		$this->_get_reg_status_selection(); // sets up the template args for the reg status array for the transaction.
1149
+
1150
+		$this->_template_args['transaction_form_url'] = add_query_arg(
1151
+			array(
1152
+				'action'  => 'edit_transaction',
1153
+				'process' => 'transaction',
1154
+			),
1155
+			TXN_ADMIN_URL
1156
+		);
1157
+		$this->_template_args['apply_payment_form_url'] = add_query_arg(
1158
+			array(
1159
+				'page'   => 'espresso_transactions',
1160
+				'action' => 'espresso_apply_payment',
1161
+			),
1162
+			WP_AJAX_URL
1163
+		);
1164
+		$this->_template_args['delete_payment_form_url'] = add_query_arg(
1165
+			array(
1166
+				'page'   => 'espresso_transactions',
1167
+				'action' => 'espresso_delete_payment',
1168
+			),
1169
+			WP_AJAX_URL
1170
+		);
1171
+
1172
+		$this->_template_args['action_buttons'] = $this->getActionButtons($this->_transaction);
1173
+
1174
+		// 'espresso_delete_payment_nonce'
1175
+
1176
+		$template_path = TXN_TEMPLATE_PATH . 'txn_admin_details_main_meta_box_txn_details.template.php';
1177
+		echo EEH_Template::display_template($template_path, $this->_template_args, true);
1178
+	}
1179
+
1180
+
1181
+	/**
1182
+	 * _get_registration_payment_IDs
1183
+	 *    generates an array of Payment IDs and their corresponding Registration IDs
1184
+	 *
1185
+	 * @access protected
1186
+	 * @param EE_Payment[] $payments
1187
+	 * @return array
1188
+	 * @throws EE_Error
1189
+	 * @throws InvalidArgumentException
1190
+	 * @throws InvalidDataTypeException
1191
+	 * @throws InvalidInterfaceException
1192
+	 * @throws ReflectionException
1193
+	 */
1194
+	protected function _get_registration_payment_IDs($payments = array())
1195
+	{
1196
+		$existing_reg_payments = array();
1197
+		// get all reg payments for these payments
1198
+		$reg_payments = EEM_Registration_Payment::instance()->get_all(
1199
+			array(
1200
+				array(
1201
+					'PAY_ID' => array(
1202
+						'IN',
1203
+						array_keys($payments),
1204
+					),
1205
+				),
1206
+			)
1207
+		);
1208
+		if (! empty($reg_payments)) {
1209
+			foreach ($payments as $payment) {
1210
+				if (! $payment instanceof EE_Payment) {
1211
+					continue;
1212
+				} elseif (! isset($existing_reg_payments[ $payment->ID() ])) {
1213
+					$existing_reg_payments[ $payment->ID() ] = array();
1214
+				}
1215
+				foreach ($reg_payments as $reg_payment) {
1216
+					if ($reg_payment instanceof EE_Registration_Payment
1217
+						&& $reg_payment->payment_ID() === $payment->ID()
1218
+					) {
1219
+						$existing_reg_payments[ $payment->ID() ][] = $reg_payment->registration_ID();
1220
+					}
1221
+				}
1222
+			}
1223
+		}
1224
+
1225
+		return $existing_reg_payments;
1226
+	}
1227
+
1228
+
1229
+	/**
1230
+	 * _get_registrations_to_apply_payment_to
1231
+	 *    generates HTML for displaying a series of checkboxes in the admin payment modal window
1232
+	 * which allows the admin to only apply the payment to the specific registrations
1233
+	 *
1234
+	 * @access protected
1235
+	 * @return void
1236
+	 * @throws EE_Error
1237
+	 * @throws InvalidArgumentException
1238
+	 * @throws InvalidDataTypeException
1239
+	 * @throws InvalidInterfaceException
1240
+	 * @throws ReflectionException
1241
+	 */
1242
+	protected function _get_registrations_to_apply_payment_to()
1243
+	{
1244
+		// we want any registration with an active status (ie: not deleted or cancelled)
1245
+		$query_params = array(
1246
+			array(
1247
+				'STS_ID' => array(
1248
+					'IN',
1249
+					array(
1250
+						EEM_Registration::status_id_approved,
1251
+						EEM_Registration::status_id_pending_payment,
1252
+						EEM_Registration::status_id_not_approved,
1253
+					),
1254
+				),
1255
+			),
1256
+		);
1257
+		$registrations_to_apply_payment_to = EEH_HTML::br() . EEH_HTML::div(
1258
+			'',
1259
+			'txn-admin-apply-payment-to-registrations-dv',
1260
+			'',
1261
+			'clear: both; margin: 1.5em 0 0; display: none;'
1262
+		);
1263
+		$registrations_to_apply_payment_to .= EEH_HTML::br() . EEH_HTML::div('', '', 'admin-primary-mbox-tbl-wrap');
1264
+		$registrations_to_apply_payment_to .= EEH_HTML::table('', '', 'admin-primary-mbox-tbl');
1265
+		$registrations_to_apply_payment_to .= EEH_HTML::thead(
1266
+			EEH_HTML::tr(
1267
+				EEH_HTML::th(esc_html__('ID', 'event_espresso')) .
1268
+				EEH_HTML::th(esc_html__('Registrant', 'event_espresso')) .
1269
+				EEH_HTML::th(esc_html__('Ticket', 'event_espresso')) .
1270
+				EEH_HTML::th(esc_html__('Event', 'event_espresso')) .
1271
+				EEH_HTML::th(esc_html__('Paid', 'event_espresso'), '', 'txn-admin-payment-paid-td jst-cntr') .
1272
+				EEH_HTML::th(esc_html__('Owing', 'event_espresso'), '', 'txn-admin-payment-owing-td jst-cntr') .
1273
+				EEH_HTML::th(esc_html__('Apply', 'event_espresso'), '', 'jst-cntr')
1274
+			)
1275
+		);
1276
+		$registrations_to_apply_payment_to .= EEH_HTML::tbody();
1277
+		// get registrations for TXN
1278
+		$registrations = $this->_transaction->registrations($query_params);
1279
+		$existing_reg_payments = $this->_template_args['existing_reg_payments'];
1280
+		foreach ($registrations as $registration) {
1281
+			if ($registration instanceof EE_Registration) {
1282
+				$attendee_name = $registration->attendee() instanceof EE_Attendee
1283
+					? $registration->attendee()->full_name()
1284
+					: esc_html__('Unknown Attendee', 'event_espresso');
1285
+				$owing = $registration->final_price() - $registration->paid();
1286
+				$taxable = $registration->ticket()->taxable()
1287
+					? ' <span class="smaller-text lt-grey-text"> ' . esc_html__('+ tax', 'event_espresso') . '</span>'
1288
+					: '';
1289
+				$checked = empty($existing_reg_payments)
1290
+						   || in_array($registration->ID(), $existing_reg_payments, true)
1291
+					? ' checked="checked"'
1292
+					: '';
1293
+				$disabled = $registration->final_price() > 0 ? '' : ' disabled';
1294
+				$registrations_to_apply_payment_to .= EEH_HTML::tr(
1295
+					EEH_HTML::td($registration->ID()) .
1296
+					EEH_HTML::td($attendee_name) .
1297
+					EEH_HTML::td(
1298
+						$registration->ticket()->name() . ' : ' . $registration->ticket()->pretty_price() . $taxable
1299
+					) .
1300
+					EEH_HTML::td($registration->event_name()) .
1301
+					EEH_HTML::td($registration->pretty_paid(), '', 'txn-admin-payment-paid-td jst-cntr') .
1302
+					EEH_HTML::td(
1303
+						EEH_Template::format_currency($owing),
1304
+						'',
1305
+						'txn-admin-payment-owing-td jst-cntr'
1306
+					) .
1307
+					EEH_HTML::td(
1308
+						'<input type="checkbox" value="' . $registration->ID()
1309
+						. '" name="txn_admin_payment[registrations]"'
1310
+						. $checked . $disabled . '>',
1311
+						'',
1312
+						'jst-cntr'
1313
+					),
1314
+					'apply-payment-registration-row-' . $registration->ID()
1315
+				);
1316
+			}
1317
+		}
1318
+		$registrations_to_apply_payment_to .= EEH_HTML::tbodyx();
1319
+		$registrations_to_apply_payment_to .= EEH_HTML::tablex();
1320
+		$registrations_to_apply_payment_to .= EEH_HTML::divx();
1321
+		$registrations_to_apply_payment_to .= EEH_HTML::p(
1322
+			esc_html__(
1323
+				'The payment will only be applied to the registrations that have a check mark in their corresponding check box. Checkboxes for free registrations have been disabled.',
1324
+				'event_espresso'
1325
+			),
1326
+			'',
1327
+			'clear description'
1328
+		);
1329
+		$registrations_to_apply_payment_to .= EEH_HTML::divx();
1330
+		$this->_template_args['registrations_to_apply_payment_to'] = $registrations_to_apply_payment_to;
1331
+	}
1332
+
1333
+
1334
+	/**
1335
+	 * _get_reg_status_selection
1336
+	 *
1337
+	 * @todo   this will need to be adjusted either once MER comes along OR we move default reg status to tickets
1338
+	 *         instead of events.
1339
+	 * @access protected
1340
+	 * @return void
1341
+	 * @throws EE_Error
1342
+	 */
1343
+	protected function _get_reg_status_selection()
1344
+	{
1345
+		// first get all possible statuses
1346
+		$statuses = EEM_Registration::reg_status_array(array(), true);
1347
+		// let's add a "don't change" option.
1348
+		$status_array['NAN'] = esc_html__('Leave the Same', 'event_espresso');
1349
+		$status_array = array_merge($status_array, $statuses);
1350
+		$this->_template_args['status_change_select'] = EEH_Form_Fields::select_input(
1351
+			'txn_reg_status_change[reg_status]',
1352
+			$status_array,
1353
+			'NAN',
1354
+			'id="txn-admin-payment-reg-status-inp"',
1355
+			'txn-reg-status-change-reg-status'
1356
+		);
1357
+		$this->_template_args['delete_status_change_select'] = EEH_Form_Fields::select_input(
1358
+			'delete_txn_reg_status_change[reg_status]',
1359
+			$status_array,
1360
+			'NAN',
1361
+			'delete-txn-admin-payment-reg-status-inp',
1362
+			'delete-txn-reg-status-change-reg-status'
1363
+		);
1364
+	}
1365
+
1366
+
1367
+	/**
1368
+	 *    _get_payment_methods
1369
+	 * Gets all the payment methods available generally, or the ones that are already
1370
+	 * selected on these payments (in case their payment methods are no longer active).
1371
+	 * Has the side-effect of updating the template args' payment_methods item
1372
+	 *
1373
+	 * @access private
1374
+	 * @param EE_Payment[] to show on this page
1375
+	 * @return void
1376
+	 * @throws EE_Error
1377
+	 * @throws InvalidArgumentException
1378
+	 * @throws InvalidDataTypeException
1379
+	 * @throws InvalidInterfaceException
1380
+	 * @throws ReflectionException
1381
+	 */
1382
+	private function _get_payment_methods($payments = array())
1383
+	{
1384
+		$payment_methods_of_payments = array();
1385
+		foreach ($payments as $payment) {
1386
+			if ($payment instanceof EE_Payment) {
1387
+				$payment_methods_of_payments[] = $payment->ID();
1388
+			}
1389
+		}
1390
+		if ($payment_methods_of_payments) {
1391
+			$query_args = array(
1392
+				array(
1393
+					'OR*payment_method_for_payment' => array(
1394
+						'PMD_ID'    => array('IN', $payment_methods_of_payments),
1395
+						'PMD_scope' => array('LIKE', '%' . EEM_Payment_Method::scope_admin . '%'),
1396
+					),
1397
+				),
1398
+			);
1399
+		} else {
1400
+			$query_args = array(array('PMD_scope' => array('LIKE', '%' . EEM_Payment_Method::scope_admin . '%')));
1401
+		}
1402
+		$this->_template_args['payment_methods'] = EEM_Payment_Method::instance()->get_all($query_args);
1403
+	}
1404
+
1405
+
1406
+	/**
1407
+	 * txn_attendees_meta_box
1408
+	 *    generates HTML for the Attendees Transaction main meta box
1409
+	 *
1410
+	 * @access public
1411
+	 * @param WP_Post $post
1412
+	 * @param array   $metabox
1413
+	 * @return void
1414
+	 * @throws DomainException
1415
+	 * @throws EE_Error
1416
+	 * @throws InvalidArgumentException
1417
+	 * @throws InvalidDataTypeException
1418
+	 * @throws InvalidInterfaceException
1419
+	 * @throws ReflectionException
1420
+	 */
1421
+	public function txn_attendees_meta_box($post, $metabox = array('args' => array()))
1422
+	{
1423
+
1424
+		/** @noinspection NonSecureExtractUsageInspection */
1425
+		extract($metabox['args']);
1426
+		$this->_template_args['post'] = $post;
1427
+		$this->_template_args['event_attendees'] = array();
1428
+		// process items in cart
1429
+		$line_items = $this->_transaction->get_many_related(
1430
+			'Line_Item',
1431
+			array(array('LIN_type' => 'line-item'))
1432
+		);
1433
+		if (! empty($line_items)) {
1434
+			foreach ($line_items as $item) {
1435
+				if ($item instanceof EE_Line_Item) {
1436
+					switch ($item->OBJ_type()) {
1437
+						case 'Event':
1438
+							break;
1439
+						case 'Ticket':
1440
+							$ticket = $item->ticket();
1441
+							// right now we're only handling tickets here.
1442
+							// Cause its expected that only tickets will have attendees right?
1443
+							if (! $ticket instanceof EE_Ticket) {
1444
+								break;
1445
+							}
1446
+							try {
1447
+								$event_name = $ticket->get_event_name();
1448
+							} catch (Exception $e) {
1449
+								EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
1450
+								$event_name = esc_html__('Unknown Event', 'event_espresso');
1451
+							}
1452
+							$event_name .= ' - ' . $item->name();
1453
+							$ticket_price = EEH_Template::format_currency($item->unit_price());
1454
+							// now get all of the registrations for this transaction that use this ticket
1455
+							$registrations = $ticket->registrations(
1456
+								array(array('TXN_ID' => $this->_transaction->ID()))
1457
+							);
1458
+							foreach ($registrations as $registration) {
1459
+								if (! $registration instanceof EE_Registration) {
1460
+									break;
1461
+								}
1462
+								$this->_template_args['event_attendees'][ $registration->ID() ]['STS_ID']
1463
+									= $registration->status_ID();
1464
+								$this->_template_args['event_attendees'][ $registration->ID() ]['att_num']
1465
+									= $registration->count();
1466
+								$this->_template_args['event_attendees'][ $registration->ID() ]['event_ticket_name']
1467
+									= $event_name;
1468
+								$this->_template_args['event_attendees'][ $registration->ID() ]['ticket_price']
1469
+									= $ticket_price;
1470
+								// attendee info
1471
+								$attendee = $registration->get_first_related('Attendee');
1472
+								if ($attendee instanceof EE_Attendee) {
1473
+									$this->_template_args['event_attendees'][ $registration->ID() ]['att_id']
1474
+										= $attendee->ID();
1475
+									$this->_template_args['event_attendees'][ $registration->ID() ]['attendee']
1476
+										= $attendee->full_name();
1477
+									$this->_template_args['event_attendees'][ $registration->ID() ]['email']
1478
+										= '<a href="mailto:' . $attendee->email() . '?subject=' . $event_name
1479
+										  . esc_html__(
1480
+											  ' Event',
1481
+											  'event_espresso'
1482
+										  )
1483
+										  . '">' . $attendee->email() . '</a>';
1484
+									$this->_template_args['event_attendees'][ $registration->ID() ]['address']
1485
+										= EEH_Address::format($attendee, 'inline', false, false);
1486
+								} else {
1487
+									$this->_template_args['event_attendees'][ $registration->ID() ]['att_id'] = '';
1488
+									$this->_template_args['event_attendees'][ $registration->ID() ]['attendee'] = '';
1489
+									$this->_template_args['event_attendees'][ $registration->ID() ]['email'] = '';
1490
+									$this->_template_args['event_attendees'][ $registration->ID() ]['address'] = '';
1491
+								}
1492
+							}
1493
+							break;
1494
+					}
1495
+				}
1496
+			}
1497
+
1498
+			$this->_template_args['transaction_form_url'] = add_query_arg(
1499
+				array(
1500
+					'action'  => 'edit_transaction',
1501
+					'process' => 'attendees',
1502
+				),
1503
+				TXN_ADMIN_URL
1504
+			);
1505
+			echo EEH_Template::display_template(
1506
+				TXN_TEMPLATE_PATH . 'txn_admin_details_main_meta_box_attendees.template.php',
1507
+				$this->_template_args,
1508
+				true
1509
+			);
1510
+		} else {
1511
+			echo sprintf(
1512
+				esc_html__(
1513
+					'%1$sFor some reason, there are no attendees registered for this transaction. Likely the registration was abandoned in process.%2$s',
1514
+					'event_espresso'
1515
+				),
1516
+				'<p class="important-notice">',
1517
+				'</p>'
1518
+			);
1519
+		}
1520
+	}
1521
+
1522
+
1523
+	/**
1524
+	 * txn_registrant_side_meta_box
1525
+	 * generates HTML for the Edit Transaction side meta box
1526
+	 *
1527
+	 * @access public
1528
+	 * @return void
1529
+	 * @throws DomainException
1530
+	 * @throws EE_Error
1531
+	 * @throws InvalidArgumentException
1532
+	 * @throws InvalidDataTypeException
1533
+	 * @throws InvalidInterfaceException
1534
+	 * @throws ReflectionException
1535
+	 */
1536
+	public function txn_registrant_side_meta_box()
1537
+	{
1538
+		$primary_att = $this->_transaction->primary_registration() instanceof EE_Registration
1539
+			? $this->_transaction->primary_registration()->get_first_related('Attendee')
1540
+			: null;
1541
+		if (! $primary_att instanceof EE_Attendee) {
1542
+			$this->_template_args['no_attendee_message'] = esc_html__(
1543
+				'There is no attached contact for this transaction.  The transaction either failed due to an error or was abandoned.',
1544
+				'event_espresso'
1545
+			);
1546
+			$primary_att = EEM_Attendee::instance()->create_default_object();
1547
+		}
1548
+		$this->_template_args['ATT_ID'] = $primary_att->ID();
1549
+		$this->_template_args['prime_reg_fname'] = $primary_att->fname();
1550
+		$this->_template_args['prime_reg_lname'] = $primary_att->lname();
1551
+		$this->_template_args['prime_reg_email'] = $primary_att->email();
1552
+		$this->_template_args['prime_reg_phone'] = $primary_att->phone();
1553
+		$this->_template_args['edit_attendee_url'] = EE_Admin_Page::add_query_args_and_nonce(
1554
+			array(
1555
+				'action' => 'edit_attendee',
1556
+				'post'   => $primary_att->ID(),
1557
+			),
1558
+			REG_ADMIN_URL
1559
+		);
1560
+		// get formatted address for registrant
1561
+		$this->_template_args['formatted_address'] = EEH_Address::format($primary_att);
1562
+		echo EEH_Template::display_template(
1563
+			TXN_TEMPLATE_PATH . 'txn_admin_details_side_meta_box_registrant.template.php',
1564
+			$this->_template_args,
1565
+			true
1566
+		);
1567
+	}
1568
+
1569
+
1570
+	/**
1571
+	 * txn_billing_info_side_meta_box
1572
+	 *    generates HTML for the Edit Transaction side meta box
1573
+	 *
1574
+	 * @access public
1575
+	 * @return void
1576
+	 * @throws DomainException
1577
+	 * @throws EE_Error
1578
+	 */
1579
+	public function txn_billing_info_side_meta_box()
1580
+	{
1581
+
1582
+		$this->_template_args['billing_form'] = $this->_transaction->billing_info();
1583
+		$this->_template_args['billing_form_url'] = add_query_arg(
1584
+			array('action' => 'edit_transaction', 'process' => 'billing'),
1585
+			TXN_ADMIN_URL
1586
+		);
1587
+
1588
+		$template_path = TXN_TEMPLATE_PATH . 'txn_admin_details_side_meta_box_billing_info.template.php';
1589
+		echo EEH_Template::display_template($template_path, $this->_template_args, true);
1590
+	}
1591
+
1592
+
1593
+	/**
1594
+	 * apply_payments_or_refunds
1595
+	 *    registers a payment or refund made towards a transaction
1596
+	 *
1597
+	 * @access public
1598
+	 * @return void
1599
+	 * @throws EE_Error
1600
+	 * @throws InvalidArgumentException
1601
+	 * @throws ReflectionException
1602
+	 * @throws RuntimeException
1603
+	 * @throws InvalidDataTypeException
1604
+	 * @throws InvalidInterfaceException
1605
+	 */
1606
+	public function apply_payments_or_refunds()
1607
+	{
1608
+		$json_response_data = array('return_data' => false);
1609
+		$valid_data = $this->_validate_payment_request_data();
1610
+		$has_access = EE_Registry::instance()->CAP->current_user_can(
1611
+			'ee_edit_payments',
1612
+			'apply_payment_or_refund_from_registration_details'
1613
+		);
1614
+		if (! empty($valid_data) && $has_access) {
1615
+			$PAY_ID = $valid_data['PAY_ID'];
1616
+			// save  the new payment
1617
+			$payment = $this->_create_payment_from_request_data($valid_data);
1618
+			// get the TXN for this payment
1619
+			$transaction = $payment->transaction();
1620
+			// verify transaction
1621
+			if ($transaction instanceof EE_Transaction) {
1622
+				// calculate_total_payments_and_update_status
1623
+				$this->_process_transaction_payments($transaction);
1624
+				$REG_IDs = $this->_get_REG_IDs_to_apply_payment_to($payment);
1625
+				$this->_remove_existing_registration_payments($payment, $PAY_ID);
1626
+				// apply payment to registrations (if applicable)
1627
+				if (! empty($REG_IDs)) {
1628
+					$this->_update_registration_payments($transaction, $payment, $REG_IDs);
1629
+					$this->_maybe_send_notifications();
1630
+					// now process status changes for the same registrations
1631
+					$this->_process_registration_status_change($transaction, $REG_IDs);
1632
+				}
1633
+				$this->_maybe_send_notifications($payment);
1634
+				// prepare to render page
1635
+				$json_response_data['return_data'] = $this->_build_payment_json_response($payment, $REG_IDs);
1636
+				do_action(
1637
+					'AHEE__Transactions_Admin_Page__apply_payments_or_refund__after_recording',
1638
+					$transaction,
1639
+					$payment
1640
+				);
1641
+			} else {
1642
+				EE_Error::add_error(
1643
+					esc_html__(
1644
+						'A valid Transaction for this payment could not be retrieved.',
1645
+						'event_espresso'
1646
+					),
1647
+					__FILE__,
1648
+					__FUNCTION__,
1649
+					__LINE__
1650
+				);
1651
+			}
1652
+		} elseif ($has_access) {
1653
+			EE_Error::add_error(
1654
+				esc_html__(
1655
+					'The payment form data could not be processed. Please try again.',
1656
+					'event_espresso'
1657
+				),
1658
+				__FILE__,
1659
+				__FUNCTION__,
1660
+				__LINE__
1661
+			);
1662
+		} else {
1663
+			EE_Error::add_error(
1664
+				esc_html__(
1665
+					'You do not have access to apply payments or refunds to a registration.',
1666
+					'event_espresso'
1667
+				),
1668
+				__FILE__,
1669
+				__FUNCTION__,
1670
+				__LINE__
1671
+			);
1672
+		}
1673
+		$notices = EE_Error::get_notices(
1674
+			false,
1675
+			false,
1676
+			false
1677
+		);
1678
+		$this->_template_args = array(
1679
+			'data'    => $json_response_data,
1680
+			'error'   => $notices['errors'],
1681
+			'success' => $notices['success'],
1682
+		);
1683
+		$this->_return_json();
1684
+	}
1685
+
1686
+
1687
+	/**
1688
+	 * _validate_payment_request_data
1689
+	 *
1690
+	 * @return array
1691
+	 * @throws EE_Error
1692
+	 * @throws InvalidArgumentException
1693
+	 * @throws InvalidDataTypeException
1694
+	 * @throws InvalidInterfaceException
1695
+	 */
1696
+	protected function _validate_payment_request_data()
1697
+	{
1698
+		if (! isset($this->_req_data['txn_admin_payment'])) {
1699
+			return array();
1700
+		}
1701
+		$payment_form = $this->_generate_payment_form_section();
1702
+		try {
1703
+			if ($payment_form->was_submitted()) {
1704
+				$payment_form->receive_form_submission();
1705
+				if (! $payment_form->is_valid()) {
1706
+					$submission_error_messages = array();
1707
+					foreach ($payment_form->get_validation_errors_accumulated() as $validation_error) {
1708
+						if ($validation_error instanceof EE_Validation_Error) {
1709
+							$submission_error_messages[] = sprintf(
1710
+								_x('%s : %s', 'Form Section Name : Form Validation Error', 'event_espresso'),
1711
+								$validation_error->get_form_section()->html_label_text(),
1712
+								$validation_error->getMessage()
1713
+							);
1714
+						}
1715
+					}
1716
+					EE_Error::add_error(
1717
+						implode('<br />', $submission_error_messages),
1718
+						__FILE__,
1719
+						__FUNCTION__,
1720
+						__LINE__
1721
+					);
1722
+					return array();
1723
+				}
1724
+			}
1725
+		} catch (EE_Error $e) {
1726
+			EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
1727
+			return array();
1728
+		}
1729
+
1730
+		return $payment_form->valid_data();
1731
+	}
1732
+
1733
+
1734
+	/**
1735
+	 * _generate_payment_form_section
1736
+	 *
1737
+	 * @return EE_Form_Section_Proper
1738
+	 * @throws EE_Error
1739
+	 */
1740
+	protected function _generate_payment_form_section()
1741
+	{
1742
+		return new EE_Form_Section_Proper(
1743
+			array(
1744
+				'name'        => 'txn_admin_payment',
1745
+				'subsections' => array(
1746
+					'PAY_ID'          => new EE_Text_Input(
1747
+						array(
1748
+							'default'               => 0,
1749
+							'required'              => false,
1750
+							'html_label_text'       => esc_html__('Payment ID', 'event_espresso'),
1751
+							'validation_strategies' => array(new EE_Int_Normalization()),
1752
+						)
1753
+					),
1754
+					'TXN_ID'          => new EE_Text_Input(
1755
+						array(
1756
+							'default'               => 0,
1757
+							'required'              => true,
1758
+							'html_label_text'       => esc_html__('Transaction ID', 'event_espresso'),
1759
+							'validation_strategies' => array(new EE_Int_Normalization()),
1760
+						)
1761
+					),
1762
+					'type'            => new EE_Text_Input(
1763
+						array(
1764
+							'default'               => 1,
1765
+							'required'              => true,
1766
+							'html_label_text'       => esc_html__('Payment or Refund', 'event_espresso'),
1767
+							'validation_strategies' => array(new EE_Int_Normalization()),
1768
+						)
1769
+					),
1770
+					'amount'          => new EE_Text_Input(
1771
+						array(
1772
+							'default'               => 0,
1773
+							'required'              => true,
1774
+							'html_label_text'       => esc_html__('Payment amount', 'event_espresso'),
1775
+							'validation_strategies' => array(new EE_Float_Normalization()),
1776
+						)
1777
+					),
1778
+					'status'          => new EE_Text_Input(
1779
+						array(
1780
+							'default'         => EEM_Payment::status_id_approved,
1781
+							'required'        => true,
1782
+							'html_label_text' => esc_html__('Payment status', 'event_espresso'),
1783
+						)
1784
+					),
1785
+					'PMD_ID'          => new EE_Text_Input(
1786
+						array(
1787
+							'default'               => 2,
1788
+							'required'              => true,
1789
+							'html_label_text'       => esc_html__('Payment Method', 'event_espresso'),
1790
+							'validation_strategies' => array(new EE_Int_Normalization()),
1791
+						)
1792
+					),
1793
+					'date'            => new EE_Text_Input(
1794
+						array(
1795
+							'default'         => time(),
1796
+							'required'        => true,
1797
+							'html_label_text' => esc_html__('Payment date', 'event_espresso'),
1798
+						)
1799
+					),
1800
+					'txn_id_chq_nmbr' => new EE_Text_Input(
1801
+						array(
1802
+							'default'               => '',
1803
+							'required'              => false,
1804
+							'html_label_text'       => esc_html__('Transaction or Cheque Number', 'event_espresso'),
1805
+							'validation_strategies' => array(
1806
+								new EE_Max_Length_Validation_Strategy(
1807
+									esc_html__('Input too long', 'event_espresso'),
1808
+									100
1809
+								),
1810
+							),
1811
+						)
1812
+					),
1813
+					'po_number'       => new EE_Text_Input(
1814
+						array(
1815
+							'default'               => '',
1816
+							'required'              => false,
1817
+							'html_label_text'       => esc_html__('Purchase Order Number', 'event_espresso'),
1818
+							'validation_strategies' => array(
1819
+								new EE_Max_Length_Validation_Strategy(
1820
+									esc_html__('Input too long', 'event_espresso'),
1821
+									100
1822
+								),
1823
+							),
1824
+						)
1825
+					),
1826
+					'accounting'      => new EE_Text_Input(
1827
+						array(
1828
+							'default'               => '',
1829
+							'required'              => false,
1830
+							'html_label_text'       => esc_html__('Extra Field for Accounting', 'event_espresso'),
1831
+							'validation_strategies' => array(
1832
+								new EE_Max_Length_Validation_Strategy(
1833
+									esc_html__('Input too long', 'event_espresso'),
1834
+									100
1835
+								),
1836
+							),
1837
+						)
1838
+					),
1839
+				),
1840
+			)
1841
+		);
1842
+	}
1843
+
1844
+
1845
+	/**
1846
+	 * _create_payment_from_request_data
1847
+	 *
1848
+	 * @param array $valid_data
1849
+	 * @return EE_Payment
1850
+	 * @throws EE_Error
1851
+	 * @throws InvalidArgumentException
1852
+	 * @throws InvalidDataTypeException
1853
+	 * @throws InvalidInterfaceException
1854
+	 * @throws ReflectionException
1855
+	 */
1856
+	protected function _create_payment_from_request_data($valid_data)
1857
+	{
1858
+		$PAY_ID = $valid_data['PAY_ID'];
1859
+		// get payment amount
1860
+		$amount = $valid_data['amount'] ? abs($valid_data['amount']) : 0;
1861
+		// payments have a type value of 1 and refunds have a type value of -1
1862
+		// so multiplying amount by type will give a positive value for payments, and negative values for refunds
1863
+		$amount = $valid_data['type'] < 0 ? $amount * -1 : $amount;
1864
+		// for some reason the date string coming in has extra spaces between the date and time.  This fixes that.
1865
+		$date = $valid_data['date']
1866
+			? preg_replace('/\s+/', ' ', $valid_data['date'])
1867
+			: date('Y-m-d g:i a', current_time('timestamp'));
1868
+		$payment = EE_Payment::new_instance(
1869
+			array(
1870
+				'TXN_ID'              => $valid_data['TXN_ID'],
1871
+				'STS_ID'              => $valid_data['status'],
1872
+				'PAY_timestamp'       => $date,
1873
+				'PAY_source'          => EEM_Payment_Method::scope_admin,
1874
+				'PMD_ID'              => $valid_data['PMD_ID'],
1875
+				'PAY_amount'          => $amount,
1876
+				'PAY_txn_id_chq_nmbr' => $valid_data['txn_id_chq_nmbr'],
1877
+				'PAY_po_number'       => $valid_data['po_number'],
1878
+				'PAY_extra_accntng'   => $valid_data['accounting'],
1879
+				'PAY_details'         => $valid_data,
1880
+				'PAY_ID'              => $PAY_ID,
1881
+			),
1882
+			'',
1883
+			array('Y-m-d', 'g:i a')
1884
+		);
1885
+
1886
+		if (! $payment->save()) {
1887
+			EE_Error::add_error(
1888
+				sprintf(
1889
+					esc_html__('Payment %1$d has not been successfully saved to the database.', 'event_espresso'),
1890
+					$payment->ID()
1891
+				),
1892
+				__FILE__,
1893
+				__FUNCTION__,
1894
+				__LINE__
1895
+			);
1896
+		}
1897
+
1898
+		return $payment;
1899
+	}
1900
+
1901
+
1902
+	/**
1903
+	 * _process_transaction_payments
1904
+	 *
1905
+	 * @param \EE_Transaction $transaction
1906
+	 * @return void
1907
+	 * @throws EE_Error
1908
+	 * @throws InvalidArgumentException
1909
+	 * @throws ReflectionException
1910
+	 * @throws InvalidDataTypeException
1911
+	 * @throws InvalidInterfaceException
1912
+	 */
1913
+	protected function _process_transaction_payments(EE_Transaction $transaction)
1914
+	{
1915
+		/** @type EE_Transaction_Payments $transaction_payments */
1916
+		$transaction_payments = EE_Registry::instance()->load_class('Transaction_Payments');
1917
+		// update the transaction with this payment
1918
+		if ($transaction_payments->calculate_total_payments_and_update_status($transaction)) {
1919
+			EE_Error::add_success(
1920
+				esc_html__(
1921
+					'The payment has been processed successfully.',
1922
+					'event_espresso'
1923
+				),
1924
+				__FILE__,
1925
+				__FUNCTION__,
1926
+				__LINE__
1927
+			);
1928
+		} else {
1929
+			EE_Error::add_error(
1930
+				esc_html__(
1931
+					'The payment was processed successfully but the amount paid for the transaction was not updated.',
1932
+					'event_espresso'
1933
+				),
1934
+				__FILE__,
1935
+				__FUNCTION__,
1936
+				__LINE__
1937
+			);
1938
+		}
1939
+	}
1940
+
1941
+
1942
+	/**
1943
+	 * _get_REG_IDs_to_apply_payment_to
1944
+	 * returns a list of registration IDs that the payment will apply to
1945
+	 *
1946
+	 * @param \EE_Payment $payment
1947
+	 * @return array
1948
+	 * @throws EE_Error
1949
+	 * @throws InvalidArgumentException
1950
+	 * @throws InvalidDataTypeException
1951
+	 * @throws InvalidInterfaceException
1952
+	 * @throws ReflectionException
1953
+	 */
1954
+	protected function _get_REG_IDs_to_apply_payment_to(EE_Payment $payment)
1955
+	{
1956
+		$REG_IDs = array();
1957
+		// grab array of IDs for specific registrations to apply changes to
1958
+		if (isset($this->_req_data['txn_admin_payment']['registrations'])) {
1959
+			$REG_IDs = (array) $this->_req_data['txn_admin_payment']['registrations'];
1960
+		}
1961
+		// nothing specified ? then get all reg IDs
1962
+		if (empty($REG_IDs)) {
1963
+			$registrations = $payment->transaction()->registrations();
1964
+			$REG_IDs = ! empty($registrations)
1965
+				? array_keys($registrations)
1966
+				: $this->_get_existing_reg_payment_REG_IDs($payment);
1967
+		}
1968
+
1969
+		// ensure that REG_IDs are integers and NOT strings
1970
+		return array_map('intval', $REG_IDs);
1971
+	}
1972
+
1973
+
1974
+	/**
1975
+	 * @return array
1976
+	 */
1977
+	public function existing_reg_payment_REG_IDs()
1978
+	{
1979
+		return $this->_existing_reg_payment_REG_IDs;
1980
+	}
1981
+
1982
+
1983
+	/**
1984
+	 * @param array $existing_reg_payment_REG_IDs
1985
+	 */
1986
+	public function set_existing_reg_payment_REG_IDs($existing_reg_payment_REG_IDs = null)
1987
+	{
1988
+		$this->_existing_reg_payment_REG_IDs = $existing_reg_payment_REG_IDs;
1989
+	}
1990
+
1991
+
1992
+	/**
1993
+	 * _get_existing_reg_payment_REG_IDs
1994
+	 * returns a list of registration IDs that the payment is currently related to
1995
+	 * as recorded in the database
1996
+	 *
1997
+	 * @param \EE_Payment $payment
1998
+	 * @return array
1999
+	 * @throws EE_Error
2000
+	 * @throws InvalidArgumentException
2001
+	 * @throws InvalidDataTypeException
2002
+	 * @throws InvalidInterfaceException
2003
+	 * @throws ReflectionException
2004
+	 */
2005
+	protected function _get_existing_reg_payment_REG_IDs(EE_Payment $payment)
2006
+	{
2007
+		if ($this->existing_reg_payment_REG_IDs() === null) {
2008
+			// let's get any existing reg payment records for this payment
2009
+			$existing_reg_payment_REG_IDs = $payment->get_many_related('Registration');
2010
+			// but we only want the REG IDs, so grab the array keys
2011
+			$existing_reg_payment_REG_IDs = ! empty($existing_reg_payment_REG_IDs)
2012
+				? array_keys($existing_reg_payment_REG_IDs)
2013
+				: array();
2014
+			$this->set_existing_reg_payment_REG_IDs($existing_reg_payment_REG_IDs);
2015
+		}
2016
+
2017
+		return $this->existing_reg_payment_REG_IDs();
2018
+	}
2019
+
2020
+
2021
+	/**
2022
+	 * _remove_existing_registration_payments
2023
+	 * this calculates the difference between existing relations
2024
+	 * to the supplied payment and the new list registration IDs,
2025
+	 * removes any related registrations that no longer apply,
2026
+	 * and then updates the registration paid fields
2027
+	 *
2028
+	 * @param \EE_Payment $payment
2029
+	 * @param int         $PAY_ID
2030
+	 * @return bool;
2031
+	 * @throws EE_Error
2032
+	 * @throws InvalidArgumentException
2033
+	 * @throws ReflectionException
2034
+	 * @throws InvalidDataTypeException
2035
+	 * @throws InvalidInterfaceException
2036
+	 */
2037
+	protected function _remove_existing_registration_payments(EE_Payment $payment, $PAY_ID = 0)
2038
+	{
2039
+		// newly created payments will have nothing recorded for $PAY_ID
2040
+		if (absint($PAY_ID) === 0) {
2041
+			return false;
2042
+		}
2043
+		$existing_reg_payment_REG_IDs = $this->_get_existing_reg_payment_REG_IDs($payment);
2044
+		if (empty($existing_reg_payment_REG_IDs)) {
2045
+			return false;
2046
+		}
2047
+		/** @type EE_Transaction_Payments $transaction_payments */
2048
+		$transaction_payments = EE_Registry::instance()->load_class('Transaction_Payments');
2049
+
2050
+		return $transaction_payments->delete_registration_payments_and_update_registrations(
2051
+			$payment,
2052
+			array(
2053
+				array(
2054
+					'PAY_ID' => $payment->ID(),
2055
+					'REG_ID' => array('IN', $existing_reg_payment_REG_IDs),
2056
+				),
2057
+			)
2058
+		);
2059
+	}
2060
+
2061
+
2062
+	/**
2063
+	 * _update_registration_payments
2064
+	 * this applies the payments to the selected registrations
2065
+	 * but only if they have not already been paid for
2066
+	 *
2067
+	 * @param  EE_Transaction $transaction
2068
+	 * @param \EE_Payment     $payment
2069
+	 * @param array           $REG_IDs
2070
+	 * @return void
2071
+	 * @throws EE_Error
2072
+	 * @throws InvalidArgumentException
2073
+	 * @throws ReflectionException
2074
+	 * @throws RuntimeException
2075
+	 * @throws InvalidDataTypeException
2076
+	 * @throws InvalidInterfaceException
2077
+	 */
2078
+	protected function _update_registration_payments(
2079
+		EE_Transaction $transaction,
2080
+		EE_Payment $payment,
2081
+		$REG_IDs = array()
2082
+	) {
2083
+		// we can pass our own custom set of registrations to EE_Payment_Processor::process_registration_payments()
2084
+		// so let's do that using our set of REG_IDs from the form
2085
+		$registration_query_where_params = array(
2086
+			'REG_ID' => array('IN', $REG_IDs),
2087
+		);
2088
+		// but add in some conditions regarding payment,
2089
+		// so that we don't apply payments to registrations that are free or have already been paid for
2090
+		// but ONLY if the payment is NOT a refund ( ie: the payment amount is not negative )
2091
+		if (! $payment->is_a_refund()) {
2092
+			$registration_query_where_params['REG_final_price'] = array('!=', 0);
2093
+			$registration_query_where_params['REG_final_price*'] = array('!=', 'REG_paid', true);
2094
+		}
2095
+		$registrations = $transaction->registrations(array($registration_query_where_params));
2096
+		if (! empty($registrations)) {
2097
+			/** @type EE_Payment_Processor $payment_processor */
2098
+			$payment_processor = EE_Registry::instance()->load_core('Payment_Processor');
2099
+			$payment_processor->process_registration_payments($transaction, $payment, $registrations);
2100
+		}
2101
+	}
2102
+
2103
+
2104
+	/**
2105
+	 * _process_registration_status_change
2106
+	 * This processes requested registration status changes for all the registrations
2107
+	 * on a given transaction and (optionally) sends out notifications for the changes.
2108
+	 *
2109
+	 * @param  EE_Transaction $transaction
2110
+	 * @param array           $REG_IDs
2111
+	 * @return bool
2112
+	 * @throws EE_Error
2113
+	 * @throws InvalidArgumentException
2114
+	 * @throws ReflectionException
2115
+	 * @throws InvalidDataTypeException
2116
+	 * @throws InvalidInterfaceException
2117
+	 */
2118
+	protected function _process_registration_status_change(EE_Transaction $transaction, $REG_IDs = array())
2119
+	{
2120
+		// first if there is no change in status then we get out.
2121
+		if (! isset($this->_req_data['txn_reg_status_change']['reg_status'])
2122
+			|| $this->_req_data['txn_reg_status_change']['reg_status'] === 'NAN'
2123
+		) {
2124
+			// no error message, no change requested, just nothing to do man.
2125
+			return false;
2126
+		}
2127
+		/** @type EE_Transaction_Processor $transaction_processor */
2128
+		$transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
2129
+
2130
+		// made it here dude?  Oh WOW.  K, let's take care of changing the statuses
2131
+		return $transaction_processor->manually_update_registration_statuses(
2132
+			$transaction,
2133
+			sanitize_text_field($this->_req_data['txn_reg_status_change']['reg_status']),
2134
+			array(array('REG_ID' => array('IN', $REG_IDs)))
2135
+		);
2136
+	}
2137
+
2138
+
2139
+	/**
2140
+	 * _build_payment_json_response
2141
+	 *
2142
+	 * @access public
2143
+	 * @param \EE_Payment $payment
2144
+	 * @param array       $REG_IDs
2145
+	 * @param bool | null $delete_txn_reg_status_change
2146
+	 * @return array
2147
+	 * @throws EE_Error
2148
+	 * @throws InvalidArgumentException
2149
+	 * @throws InvalidDataTypeException
2150
+	 * @throws InvalidInterfaceException
2151
+	 * @throws ReflectionException
2152
+	 */
2153
+	protected function _build_payment_json_response(
2154
+		EE_Payment $payment,
2155
+		$REG_IDs = array(),
2156
+		$delete_txn_reg_status_change = null
2157
+	) {
2158
+		// was the payment deleted ?
2159
+		if (is_bool($delete_txn_reg_status_change)) {
2160
+			return array(
2161
+				'PAY_ID'                       => $payment->ID(),
2162
+				'amount'                       => $payment->amount(),
2163
+				'total_paid'                   => $payment->transaction()->paid(),
2164
+				'txn_status'                   => $payment->transaction()->status_ID(),
2165
+				'pay_status'                   => $payment->STS_ID(),
2166
+				'registrations'                => $this->_registration_payment_data_array($REG_IDs),
2167
+				'delete_txn_reg_status_change' => $delete_txn_reg_status_change,
2168
+			);
2169
+		} else {
2170
+			$this->_get_payment_status_array();
2171
+
2172
+			return array(
2173
+				'amount'           => $payment->amount(),
2174
+				'total_paid'       => $payment->transaction()->paid(),
2175
+				'txn_status'       => $payment->transaction()->status_ID(),
2176
+				'pay_status'       => $payment->STS_ID(),
2177
+				'PAY_ID'           => $payment->ID(),
2178
+				'STS_ID'           => $payment->STS_ID(),
2179
+				'status'           => self::$_pay_status[ $payment->STS_ID() ],
2180
+				'date'             => $payment->timestamp('Y-m-d', 'h:i a'),
2181
+				'method'           => strtoupper($payment->source()),
2182
+				'PM_ID'            => $payment->payment_method() ? $payment->payment_method()->ID() : 1,
2183
+				'gateway'          => $payment->payment_method()
2184
+					? $payment->payment_method()->admin_name()
2185
+					: esc_html__('Unknown', 'event_espresso'),
2186
+				'gateway_response' => $payment->gateway_response(),
2187
+				'txn_id_chq_nmbr'  => $payment->txn_id_chq_nmbr(),
2188
+				'po_number'        => $payment->po_number(),
2189
+				'extra_accntng'    => $payment->extra_accntng(),
2190
+				'registrations'    => $this->_registration_payment_data_array($REG_IDs),
2191
+			);
2192
+		}
2193
+	}
2194
+
2195
+
2196
+	/**
2197
+	 * delete_payment
2198
+	 *    delete a payment or refund made towards a transaction
2199
+	 *
2200
+	 * @access public
2201
+	 * @return void
2202
+	 * @throws EE_Error
2203
+	 * @throws InvalidArgumentException
2204
+	 * @throws ReflectionException
2205
+	 * @throws InvalidDataTypeException
2206
+	 * @throws InvalidInterfaceException
2207
+	 */
2208
+	public function delete_payment()
2209
+	{
2210
+		$json_response_data = array('return_data' => false);
2211
+		$PAY_ID = isset($this->_req_data['delete_txn_admin_payment']['PAY_ID'])
2212
+			? absint($this->_req_data['delete_txn_admin_payment']['PAY_ID'])
2213
+			: 0;
2214
+		$can_delete = EE_Registry::instance()->CAP->current_user_can(
2215
+			'ee_delete_payments',
2216
+			'delete_payment_from_registration_details'
2217
+		);
2218
+		if ($PAY_ID && $can_delete) {
2219
+			$delete_txn_reg_status_change = isset($this->_req_data['delete_txn_reg_status_change'])
2220
+				? $this->_req_data['delete_txn_reg_status_change']
2221
+				: false;
2222
+			$payment = EEM_Payment::instance()->get_one_by_ID($PAY_ID);
2223
+			if ($payment instanceof EE_Payment) {
2224
+				$REG_IDs = $this->_get_existing_reg_payment_REG_IDs($payment);
2225
+				/** @type EE_Transaction_Payments $transaction_payments */
2226
+				$transaction_payments = EE_Registry::instance()->load_class('Transaction_Payments');
2227
+				if ($transaction_payments->delete_payment_and_update_transaction($payment)) {
2228
+					$json_response_data['return_data'] = $this->_build_payment_json_response(
2229
+						$payment,
2230
+						$REG_IDs,
2231
+						$delete_txn_reg_status_change
2232
+					);
2233
+					if ($delete_txn_reg_status_change) {
2234
+						$this->_req_data['txn_reg_status_change'] = $delete_txn_reg_status_change;
2235
+						// MAKE sure we also add the delete_txn_req_status_change to the
2236
+						// $_REQUEST global because that's how messages will be looking for it.
2237
+						$_REQUEST['txn_reg_status_change'] = $delete_txn_reg_status_change;
2238
+						$this->_maybe_send_notifications();
2239
+						$this->_process_registration_status_change($payment->transaction(), $REG_IDs);
2240
+					}
2241
+				}
2242
+			} else {
2243
+				EE_Error::add_error(
2244
+					esc_html__('Valid Payment data could not be retrieved from the database.', 'event_espresso'),
2245
+					__FILE__,
2246
+					__FUNCTION__,
2247
+					__LINE__
2248
+				);
2249
+			}
2250
+		} elseif ($can_delete) {
2251
+			EE_Error::add_error(
2252
+				esc_html__(
2253
+					'A valid Payment ID was not received, therefore payment form data could not be loaded.',
2254
+					'event_espresso'
2255
+				),
2256
+				__FILE__,
2257
+				__FUNCTION__,
2258
+				__LINE__
2259
+			);
2260
+		} else {
2261
+			EE_Error::add_error(
2262
+				esc_html__(
2263
+					'You do not have access to delete a payment.',
2264
+					'event_espresso'
2265
+				),
2266
+				__FILE__,
2267
+				__FUNCTION__,
2268
+				__LINE__
2269
+			);
2270
+		}
2271
+		$notices = EE_Error::get_notices(false, false, false);
2272
+		$this->_template_args = array(
2273
+			'data'      => $json_response_data,
2274
+			'success'   => $notices['success'],
2275
+			'error'     => $notices['errors'],
2276
+			'attention' => $notices['attention'],
2277
+		);
2278
+		$this->_return_json();
2279
+	}
2280
+
2281
+
2282
+	/**
2283
+	 * _registration_payment_data_array
2284
+	 * adds info for 'owing' and 'paid' for each registration to the json response
2285
+	 *
2286
+	 * @access protected
2287
+	 * @param array $REG_IDs
2288
+	 * @return array
2289
+	 * @throws EE_Error
2290
+	 * @throws InvalidArgumentException
2291
+	 * @throws InvalidDataTypeException
2292
+	 * @throws InvalidInterfaceException
2293
+	 * @throws ReflectionException
2294
+	 */
2295
+	protected function _registration_payment_data_array($REG_IDs)
2296
+	{
2297
+		$registration_payment_data = array();
2298
+		// if non empty reg_ids lets get an array of registrations and update the values for the apply_payment/refund rows.
2299
+		if (! empty($REG_IDs)) {
2300
+			$registrations = EEM_Registration::instance()->get_all(array(array('REG_ID' => array('IN', $REG_IDs))));
2301
+			foreach ($registrations as $registration) {
2302
+				if ($registration instanceof EE_Registration) {
2303
+					$registration_payment_data[ $registration->ID() ] = array(
2304
+						'paid'  => $registration->pretty_paid(),
2305
+						'owing' => EEH_Template::format_currency($registration->final_price() - $registration->paid()),
2306
+					);
2307
+				}
2308
+			}
2309
+		}
2310
+
2311
+		return $registration_payment_data;
2312
+	}
2313
+
2314
+
2315
+	/**
2316
+	 * _maybe_send_notifications
2317
+	 * determines whether or not the admin has indicated that notifications should be sent.
2318
+	 * If so, will toggle a filter switch for delivering registration notices.
2319
+	 * If passed an EE_Payment object, then it will trigger payment notifications instead.
2320
+	 *
2321
+	 * @access protected
2322
+	 * @param \EE_Payment | null $payment
2323
+	 */
2324
+	protected function _maybe_send_notifications($payment = null)
2325
+	{
2326
+		switch ($payment instanceof EE_Payment) {
2327
+			// payment notifications
2328
+			case true:
2329
+				if (isset($this->_req_data['txn_payments']['send_notifications'])
2330
+					&& filter_var(
2331
+						$this->_req_data['txn_payments']['send_notifications'],
2332
+						FILTER_VALIDATE_BOOLEAN
2333
+					)
2334
+				) {
2335
+					$this->_process_payment_notification($payment);
2336
+				}
2337
+				break;
2338
+			// registration notifications
2339
+			case false:
2340
+				if (isset($this->_req_data['txn_reg_status_change']['send_notifications'])
2341
+					&& filter_var(
2342
+						$this->_req_data['txn_reg_status_change']['send_notifications'],
2343
+						FILTER_VALIDATE_BOOLEAN
2344
+					)
2345
+				) {
2346
+					add_filter('FHEE__EED_Messages___maybe_registration__deliver_notifications', '__return_true');
2347
+				}
2348
+				break;
2349
+		}
2350
+	}
2351
+
2352
+
2353
+	/**
2354
+	 * _send_payment_reminder
2355
+	 *    generates HTML for the View Transaction Details Admin page
2356
+	 *
2357
+	 * @access protected
2358
+	 * @return void
2359
+	 * @throws EE_Error
2360
+	 * @throws InvalidArgumentException
2361
+	 * @throws InvalidDataTypeException
2362
+	 * @throws InvalidInterfaceException
2363
+	 */
2364
+	protected function _send_payment_reminder()
2365
+	{
2366
+		$TXN_ID = ! empty($this->_req_data['TXN_ID']) ? absint($this->_req_data['TXN_ID']) : false;
2367
+		$transaction = EEM_Transaction::instance()->get_one_by_ID($TXN_ID);
2368
+		$query_args = isset($this->_req_data['redirect_to']) ? array(
2369
+			'action' => $this->_req_data['redirect_to'],
2370
+			'TXN_ID' => $this->_req_data['TXN_ID'],
2371
+		) : array();
2372
+		do_action(
2373
+			'AHEE__Transactions_Admin_Page___send_payment_reminder__process_admin_payment_reminder',
2374
+			$transaction
2375
+		);
2376
+		$this->_redirect_after_action(
2377
+			false,
2378
+			esc_html__('payment reminder', 'event_espresso'),
2379
+			esc_html__('sent', 'event_espresso'),
2380
+			$query_args,
2381
+			true
2382
+		);
2383
+	}
2384
+
2385
+
2386
+	/**
2387
+	 *  get_transactions
2388
+	 *    get transactions for given parameters (used by list table)
2389
+	 *
2390
+	 * @param  int     $perpage how many transactions displayed per page
2391
+	 * @param  boolean $count   return the count or objects
2392
+	 * @param string   $view
2393
+	 * @return mixed int = count || array of transaction objects
2394
+	 * @throws EE_Error
2395
+	 * @throws InvalidArgumentException
2396
+	 * @throws InvalidDataTypeException
2397
+	 * @throws InvalidInterfaceException
2398
+	 */
2399
+	public function get_transactions($perpage, $count = false, $view = '')
2400
+	{
2401
+
2402
+		$TXN = EEM_Transaction::instance();
2403
+
2404
+		$start_date = isset($this->_req_data['txn-filter-start-date'])
2405
+			? wp_strip_all_tags($this->_req_data['txn-filter-start-date'])
2406
+			: date(
2407
+				'm/d/Y',
2408
+				strtotime('-10 year')
2409
+			);
2410
+		$end_date = isset($this->_req_data['txn-filter-end-date'])
2411
+			? wp_strip_all_tags($this->_req_data['txn-filter-end-date'])
2412
+			: date('m/d/Y');
2413
+
2414
+		// make sure our timestamps start and end right at the boundaries for each day
2415
+		$start_date = date('Y-m-d', strtotime($start_date)) . ' 00:00:00';
2416
+		$end_date = date('Y-m-d', strtotime($end_date)) . ' 23:59:59';
2417
+
2418
+
2419
+		// convert to timestamps
2420
+		$start_date = strtotime($start_date);
2421
+		$end_date = strtotime($end_date);
2422
+
2423
+		// makes sure start date is the lowest value and vice versa
2424
+		$start_date = min($start_date, $end_date);
2425
+		$end_date = max($start_date, $end_date);
2426
+
2427
+		// convert to correct format for query
2428
+		$start_date = EEM_Transaction::instance()->convert_datetime_for_query(
2429
+			'TXN_timestamp',
2430
+			date('Y-m-d H:i:s', $start_date),
2431
+			'Y-m-d H:i:s'
2432
+		);
2433
+		$end_date = EEM_Transaction::instance()->convert_datetime_for_query(
2434
+			'TXN_timestamp',
2435
+			date('Y-m-d H:i:s', $end_date),
2436
+			'Y-m-d H:i:s'
2437
+		);
2438
+
2439
+
2440
+		// set orderby
2441
+		$this->_req_data['orderby'] = ! empty($this->_req_data['orderby']) ? $this->_req_data['orderby'] : '';
2442
+
2443
+		switch ($this->_req_data['orderby']) {
2444
+			case 'TXN_ID':
2445
+				$orderby = 'TXN_ID';
2446
+				break;
2447
+			case 'ATT_fname':
2448
+				$orderby = 'Registration.Attendee.ATT_fname';
2449
+				break;
2450
+			case 'event_name':
2451
+				$orderby = 'Registration.Event.EVT_name';
2452
+				break;
2453
+			default: // 'TXN_timestamp'
2454
+				$orderby = 'TXN_timestamp';
2455
+		}
2456
+
2457
+		$sort = ! empty($this->_req_data['order']) ? $this->_req_data['order'] : 'DESC';
2458
+		$current_page = ! empty($this->_req_data['paged']) ? $this->_req_data['paged'] : 1;
2459
+		$per_page = ! empty($perpage) ? $perpage : 10;
2460
+		$per_page = ! empty($this->_req_data['perpage']) ? $this->_req_data['perpage'] : $per_page;
2461
+
2462
+		$offset = ($current_page - 1) * $per_page;
2463
+		$limit = array($offset, $per_page);
2464
+
2465
+		$_where = array(
2466
+			'TXN_timestamp'          => array('BETWEEN', array($start_date, $end_date)),
2467
+			'Registration.REG_count' => 1,
2468
+		);
2469
+
2470
+		if (isset($this->_req_data['EVT_ID'])) {
2471
+			$_where['Registration.EVT_ID'] = $this->_req_data['EVT_ID'];
2472
+		}
2473
+
2474
+		if (isset($this->_req_data['s'])) {
2475
+			$search_string = '%' . $this->_req_data['s'] . '%';
2476
+			$_where['OR'] = array(
2477
+				'Registration.Event.EVT_name'         => array('LIKE', $search_string),
2478
+				'Registration.Event.EVT_desc'         => array('LIKE', $search_string),
2479
+				'Registration.Event.EVT_short_desc'   => array('LIKE', $search_string),
2480
+				'Registration.Attendee.ATT_full_name' => array('LIKE', $search_string),
2481
+				'Registration.Attendee.ATT_fname'     => array('LIKE', $search_string),
2482
+				'Registration.Attendee.ATT_lname'     => array('LIKE', $search_string),
2483
+				'Registration.Attendee.ATT_short_bio' => array('LIKE', $search_string),
2484
+				'Registration.Attendee.ATT_email'     => array('LIKE', $search_string),
2485
+				'Registration.Attendee.ATT_address'   => array('LIKE', $search_string),
2486
+				'Registration.Attendee.ATT_address2'  => array('LIKE', $search_string),
2487
+				'Registration.Attendee.ATT_city'      => array('LIKE', $search_string),
2488
+				'Registration.REG_final_price'        => array('LIKE', $search_string),
2489
+				'Registration.REG_code'               => array('LIKE', $search_string),
2490
+				'Registration.REG_count'              => array('LIKE', $search_string),
2491
+				'Registration.REG_group_size'         => array('LIKE', $search_string),
2492
+				'Registration.Ticket.TKT_name'        => array('LIKE', $search_string),
2493
+				'Registration.Ticket.TKT_description' => array('LIKE', $search_string),
2494
+				'Payment.PAY_source'                  => array('LIKE', $search_string),
2495
+				'Payment.Payment_Method.PMD_name'     => array('LIKE', $search_string),
2496
+				'TXN_session_data'                    => array('LIKE', $search_string),
2497
+				'Payment.PAY_txn_id_chq_nmbr'         => array('LIKE', $search_string),
2498
+			);
2499
+		}
2500
+
2501
+		// failed transactions
2502
+		$failed = (! empty($this->_req_data['status']) && $this->_req_data['status'] === 'failed' && ! $count)
2503
+				  || ($count && $view === 'failed');
2504
+		$abandoned = (! empty($this->_req_data['status']) && $this->_req_data['status'] === 'abandoned' && ! $count)
2505
+					 || ($count && $view === 'abandoned');
2506
+		$incomplete = (! empty($this->_req_data['status']) && $this->_req_data['status'] === 'incomplete' && ! $count)
2507
+					  || ($count && $view === 'incomplete');
2508
+
2509
+		if ($failed) {
2510
+			$_where['STS_ID'] = EEM_Transaction::failed_status_code;
2511
+		} elseif ($abandoned) {
2512
+			$_where['STS_ID'] = EEM_Transaction::abandoned_status_code;
2513
+		} elseif ($incomplete) {
2514
+			$_where['STS_ID'] = EEM_Transaction::incomplete_status_code;
2515
+		} else {
2516
+			$_where['STS_ID'] = array('!=', EEM_Transaction::failed_status_code);
2517
+			$_where['STS_ID*'] = array('!=', EEM_Transaction::abandoned_status_code);
2518
+		}
2519
+
2520
+		$query_params = apply_filters(
2521
+			'FHEE__Transactions_Admin_Page___get_transactions_query_params',
2522
+			array(
2523
+				$_where,
2524
+				'order_by'                 => array($orderby => $sort),
2525
+				'limit'                    => $limit,
2526
+				'default_where_conditions' => EEM_Base::default_where_conditions_this_only,
2527
+			),
2528
+			$this->_req_data,
2529
+			$view,
2530
+			$count
2531
+		);
2532
+
2533
+		$transactions = $count
2534
+			? $TXN->count(array($query_params[0]), 'TXN_ID', true)
2535
+			: $TXN->get_all($query_params);
2536
+
2537
+		return $transactions;
2538
+	}
2539
+
2540
+
2541
+	/**
2542
+	 * @since $VID:$
2543
+	 * @throws EE_Error
2544
+	 * @throws InvalidArgumentException
2545
+	 * @throws InvalidDataTypeException
2546
+	 * @throws InvalidInterfaceException
2547
+	 * @throws ReflectionException
2548
+	 * @throws RuntimeException
2549
+	 */
2550
+	public function recalculateLineItems()
2551
+	{
2552
+		$TXN_ID = ! empty($this->_req_data['TXN_ID']) ? absint($this->_req_data['TXN_ID']) : false;
2553
+		/** @var EE_Transaction $transaction */
2554
+		$transaction = EEM_Transaction::instance()->get_one_by_ID($TXN_ID);
2555
+		$total_line_item = $transaction->total_line_item(false);
2556
+		$success = false;
2557
+		if ($total_line_item instanceof EE_Line_Item) {
2558
+			EEH_Line_Item::resetIsTaxableForTickets($total_line_item);
2559
+			$success = EEH_Line_Item::apply_taxes($total_line_item);
2560
+		}
2561
+		if ($success
2562
+			&& EEH_Money::compare_floats($transaction->total(), $total_line_item->total(), '!=')
2563
+		) {
2564
+			$transaction->set_total($total_line_item->total());
2565
+			$success = $transaction->update_status_based_on_total_paid();
2566
+		}
2567
+		$this->_redirect_after_action(
2568
+			(bool) $success,
2569
+			esc_html__('Transaction taxes and totals', 'event_espresso'),
2570
+			esc_html__('recalculated', 'event_espresso'),
2571
+			isset($this->_req_data['redirect_to'])
2572
+				? array(
2573
+				'action' => $this->_req_data['redirect_to'],
2574
+				'TXN_ID' => $this->_req_data['TXN_ID'],
2575
+			)
2576
+				: array(),
2577
+			true
2578
+		);
2579
+	}
2580 2580
 }
Please login to merge, or discard this patch.